Routing
Observable Framework uses file-based routing: each page in your project has a corresponding Markdown file (.md
) of the same name. In addition to pages, you can have importable JavaScript modules (.js
), data loaders for generating data snapshots (e.g., .csv.py
), and static assets such as images and files (e.g., .png
).
A typical project looks like this:
.
├─ docs
│ ├─ .observablehq
│ │ └─ cache
│ ├─ components
│ │ └─ dotmap.js
│ ├─ data
│ │ └─ quakes.csv.ts
│ ├─ quakes.md
│ └─ index.md
├─ .gitignore
├─ README.md
├─ observablehq.config.ts
├─ yarn.lock
└─ package.json
docs
This is the “source root” — where your source files live. It doesn’t have to be named docs
, but that’s the default; you can change it using the root config option. Pages go here. Each page is a Markdown file. Observable 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.
docs/.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 docs/.observablehq/cache
to clean the cache and force data loaders to re-run.
docs/.observablehq/deploy.json
This file is autogenerated. If you deploy your project to the Observable platform, we’ll save some information here to make it easier to redeploy next time.
docs/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.
docs/data
You can put data loaders or static files anywhere in your source root, but we recommend putting them here.
docs/index.md
This is the home page for your site. You can have as many additional pages as you’d like, but you should always have a home page, too.
observablehq.config.ts
This is the project configuration file, such as the pages and sections in the sidebar navigation, and the project’s title. The config file can be written in either TypeScript (.ts
) or JavaScript (.js
).
Pages
For example, here’s a simple project that only has two pages (hello.md
and index.md
) in the source root (docs
):
.
├─ docs
│ ├─ 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 for serving the site
│ ├─ hello.html
│ └─ index.html
└─ ...
For this site, routes map to files as:
/ → dist/index.html → docs/index.md
/hello → dist/hello.html → docs/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.)
Projects should always have a top-level index.md
; this is the root page of your project, and it’s what people visit by default.
Folders
Pages can live in folders. For example:
.
├─ docs
│ ├─ missions
| | ├─ index.md
| | ├─ apollo.md
│ │ └─ gemini.md
│ └─ index.md
└─ ...
With this setup, routes are served as:
/ → dist/index.html → docs/index.md
/missions/ → dist/missions/index.html → docs/missions/index.md
/missions/apollo → dist/missions/apollo.html → docs/missions/apollo.md
/missions/gemini → dist/missions/gemini.html → docs/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:
.
├─ docs
│ ├─ missions
| | ├─ apollo.md
│ │ └─ gemini.md
│ ├─ missions.md
│ └─ index.md
└─ ...
Now routes are served as:
/ → dist/index.html → docs/index.md
/missions → dist/missions.html → docs/missions.md
/missions/apollo → dist/missions/apollo.html → docs/missions/apollo.md
/missions/gemini → dist/missions/gemini.html → docs/missions/gemini.md
Imports
If you use a static import
(or a dynamic import with a static string literal as the module specifier) to import a local JavaScript file, the imported module will be copied to the output root (dist
) during build, too.
For example, if you have the following source root:
.
├─ docs
│ ├─ chart.js
│ └─ index.md
└─ ...
And index.md
includes a JavaScript code block that says:
import {Chart} from "./chart.js";
The resulting output root is:
.
├─ dist
│ ├─ _import
│ │ └─ chart.js
│ ├─ _observablehq
│ │ └─ ... # additional assets for serving the site
│ └─ index.html
└─ ...
The import declaration is automatically rewritten during build to point to ./_import/chart.js
instead of ./chart.js
. (In the future #260, Observable Framework will add a content hash to the imported module name for cache-breaking.)
Use a leading slash to denote paths relative to the source root, such as /chart.js
instead of ./chart.js
. This allows you to use the same path to import a module from anywhere, even in nested folders. Observable Framework always generates relative links so that the generated site can be served under a base path.
Files
If you use FileAttachment
, attached files live in the source root (docs
) alongside your Markdown pages. For example, say index.md
has some JavaScript code that references FileAttachment("quakes.csv")
:
.
├─ docs
│ ├─ index.md
│ └─ quakes.csv
└─ ...
Any files referenced by FileAttachment
will automatically be copied to the _file
folder under the output root (dist
), here resulting in:
.
├─ dist
│ ├─ _file
│ │ └─ quakes.csv
│ ├─ _observablehq
│ │ └─ ... # additional assets for serving the site
│ └─ index.html
└─ ...
FileAttachment
references are automatically rewritten during build; for example, a reference to quakes.csv
might be replaced with _file/quakes.csv
. (In the future #260, Observable Framework will add a content hash to the attached file name for cache-breaking.) Only the files you reference statically are copied to the output root (dist
), so nothing extra or unused is included in the built site.
Imported modules can use FileAttachment
, too. In this case, the path to the file is relative to the importing module in the same fashion as import
; this is accomplished by resolving relative paths at runtime with import.meta.url
.
Some additional assets are automatically promoted to file attachments and copied to _file
. For example, if you have a <link rel="stylesheet" href="style.css">
declared statically in a Markdown page, the style.css
file will be copied to _file
, too. The HTML elements eligible for file attachments are audio
, img
, link
, picture
, and video
.
Data loaders
Files can also be generated at build time by data loaders: arbitrary programs that live alongside Markdown pages and other static files in your source root (docs
). For example, if you want to generate a quakes.json
file at build time by fetching and caching data from the USGS, you could write a data loader in a shell script like so:
.
├─ docs
│ ├─ index.md
│ └─ quakes.json.sh
└─ ...
Where quakes.json.sh
is:
curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson
This will produce the following output root:
.
├─ dist
│ ├─ _file
│ │ └─ quakes.json
│ ├─ _observablehq
│ │ └─ ... # additional assets for serving the site
│ └─ index.html
└─ ...
See the data loaders documentation for additional information.
Archives
File attachments can be also be pulled from archives. The following archive extensions are supported:
.zip
- for the ZIP archive format.tar
- for tarballs.tar.gz
and.tgz
- for compressed tarballs
For example, say you have a quakes.zip
archive that includes yearly files for observed earthquakes. If you reference FileAttachment("quakes/2021.csv")
in code, Observable Framework will pull the 2021.csv
from quakes.zip
. So this source root:
.
├─ docs
│ ├─ quakes.zip
│ └─ index.md
└─ ...
Becomes this output:
.
├─ dist
│ ├─ _file
│ │ └─ quakes
│ │ └─ 2021.csv
│ ├─ _observablehq
│ │ └─ ... # additional assets for serving the site
│ └─ index.html
└─ ...