Framework
1.13.0 GitHub️ 2.6k

Project structure

A Framework project consists of a home page (index.md) and any of the following:

Pages are written in Markdown (.md) intermingled with HTML, JavaScript, SQL, and other languages. JavaScript can also be imported from local modules (.js) and npm. Data loaders can be written in Python, R, JavaScript, or any other language, and output data snapshots. These snapshots, along with static data files, can be loaded into pages to render charts, tables, and other dynamic content.

And a typical project is structured like this:

.
├─ src                    # source root
│  ├─ .observablehq
│  │  ├─ cache            # data loader cache
│  │  └─ deploy.json      # deployment metadata
│  ├─ components
│  │  └─ dotmap.js        # shared JavaScript module
│  ├─ data
│  │  └─ quakes.csv.ts    # data loader
│  ├─ index.md            # home page
│  └─ quakes.md           # page
├─ .gitignore
├─ README.md
├─ observablehq.config.js # app configuration
├─ package.json           # node package dependencies
└─ yarn.lock              # node package lockfile

src

This is the “source root” — where your source files live. It doesn’t have to be named src; you can change it using the root config option. (Prior to , the default was docs.) Pages go here. Each page is a Markdown file. Framework uses file-based routing, which means that the name of the file controls where the page is served. You can create as many pages as you like. Use folders to organize your pages.

src/.observablehq/cache

This is where the data loader cache lives. You don’t typically have to worry about this since it’s autogenerated when the first data loader is referenced. You can rm -rf src/.observablehq/cache to clean the cache and force data loaders to re-run.

src/.observablehq/deploy.json

This file is automatically generated the first time you deploy to Observable, saving some information to make it easier to redeploy next time.

src/components

You can put shared JavaScript modules anywhere in your source root, but we recommend putting them here. This helps you pull code out of Markdown files and into JavaScript, making it easier to reuse code across pages, write tests and run linters, and even share code with vanilla web applications.

src/data

You can put data loaders or static files anywhere in your source root, but we recommend putting them here.

src/index.md

This is the home page for your app. You can have as many additional pages as you’d like, but you should always have a home page, too.

observablehq.config.js

This is the app configuration file, such as the pages and sections in the sidebar navigation, and the app’s title. The config file can be written in either TypeScript (.ts) or JavaScript (.js).

Routing

Framework uses file-based routing: each page in your app has a corresponding Markdown file (.md) of the same name. For example, here’s a simple project that only has two pages (hello.md and index.md) in the source root (src):

.
├─ src
│  ├─ hello.md
│  └─ index.md
└─ ⋯

When the site is built, the output root (dist) will contain two corresponding static HTML pages (hello.html and index.html), along with a few additional assets needed for the site to work.

.
├─ dist
│  ├─ _observablehq
│  │  └─ ⋯ # additional assets
│  ├─ hello.html
│  └─ index.html
└─ ⋯

While normally a Markdown file generates only a single page, Framework also supports parameterized pages (also called dynamic routes), allowing a Markdown file to generate many pages with different data.

For this site, routes map to files as:

/      → dist/index.html → src/index.md
/hello → dist/hello.html → src/hello.md

This assumes “clean URLs” as supported by most static site servers; /hello can also be accessed as /hello.html, and / can be accessed as /index and /index.html. (Some static site servers automatically redirect to clean URLs, but we recommend being consistent when linking to your site.)

Apps should always have a top-level index.md in the source root; this is your app’s home page, and it’s what people visit by default.

Pages can live in folders. For example:

.
├─ src
│  ├─ missions
│  │  ├─ index.md
│  │  ├─ apollo.md
│  │  └─ gemini.md
│  └─ index.md
└─ ⋯

With this setup, routes are served as:

/                → dist/index.html           → src/index.md
/missions/       → dist/missions/index.html  → src/missions/index.md
/missions/apollo → dist/missions/apollo.html → src/missions/apollo.md
/missions/gemini → dist/missions/gemini.html → src/missions/gemini.md

As a variant of the above structure, you can move the missions/index.md up to a missions.md in the parent folder:

.
├─ src
│  ├─ missions
│  │  ├─ apollo.md
│  │  └─ gemini.md
│  ├─ missions.md
│  └─ index.md
└─ ⋯

Now routes are served as:

/                → dist/index.html           → src/index.md
/missions        → dist/missions.html        → src/missions.md
/missions/apollo → dist/missions/apollo.html → src/missions/apollo.md
/missions/gemini → dist/missions/gemini.html → src/missions/gemini.md