Project layout, routing and assets
How files become URLs, plus partials, error pages, assets and Sass.
A Crisptastic site is a folder. The structure of the folder decides the URLs. There is no router file.
The smallest possible site
One file is enough:
my-site/
index.html.crisp
<!doctype html>
<h1>Hello, {{= request.path }}</h1>
Run crisptastic dev inside the folder and open http://127.0.0.1:6174/.
No crisptastic.toml is needed. crisptastic.toml becomes necessary when you
want to configure something, and is required to
deploy.
A typical site
my-site/
crisptastic.toml configuration (optional while developing)
index.html.crisp /
about.html.crisp /about
blog/
index.html.crisp /blog
[slug].html.crisp /blog/:slug
sitemap.xml.crisp /sitemap.xml
404.html.crisp error page for 404 (top level only)
_partials/
_layout-open.html.crisp partial: never served
_layout-close.html.crisp partial: never served
assets/
style.scss served as /style.css
logo.svg served as /logo.svg
How a file becomes a route
The router looks at every file under the site root whose name ends in
.crisp, including in sub-folders. It does not skip hidden folders or
node_modules, so keep unrelated .crisp files out of the tree.
| Source file | URL |
|---|---|
index.html.crisp | / |
about.html.crisp | /about |
contact/index.html.crisp | /contact |
notes.md.crisp | /notes |
api/data.json.crisp | /api/data.json |
sitemap.xml.crisp | /sitemap.xml |
robots.txt.crisp | /robots.txt |
a.b.html.crisp | /a.b |
blog/[slug].html.crisp | /blog/:slug |
users/[id]/posts/[post].html.crisp | /users/:id/posts/:post |
For .html and .md pages the extension is dropped from the URL. For other
formats (.json, .xml, .txt, .css, ...) it is kept. The supported formats
are html md txt xml json css js mjs svg csv; see
Pages and templating.
Trailing slashes do not matter: /about, /about/ and //about all reach
/about, and no redirect is issued. Matching is case-sensitive. Because no
redirect is issued, a public page exists under both spellings; compare against
request.route.path (always /about), not request.path (which keeps the
slash as requested), and point canonical links at the slash-less form.
Dynamic segments
A folder or file named [name] matches exactly one non-empty path segment. The
value is available as request.route.params.name.
blog/[slug].html.crisp
<h1>{{ request.route.params.slug }}</h1>
namemay contain only letters, digits and_.[slug]must be the whole segment.[id].json.crispis an error, so useitems/[id]/index.json.crispfor/items/:id.- There are no catch-all (
[...rest]) or optional segments. - Static segments beat dynamic ones: with both
blog/new.html.crispandblog/[slug].html.crisp, the path/blog/newgoes to the static page. - Two routes that would match the same URLs, such as
about.html.crispplusabout/index.html.crisp, or[a]/xplus[b]/x, fail the build with aduplicate routeorambiguous routeerror.
Methods
A route with a [routes] declaration accepts the
methods it names. Without one, it answers GET, HEAD and POST, and PUT,
PATCH and DELETE get 405. A page decides what to do
with a POST by looking at request.method; see
Sessions and forms.
Partials: files that are never served
A file is private if its file name starts with _. The folder name is not
considered.
| Path | Result |
|---|---|
_partials/_header.html.crisp | private, includable |
_partials/header.html.crisp | public at /_partials/header |
_header.html.crisp | private |
_errors/404.html.crisp | public at /_errors/404, and not an error page |
The _partials/ folder is a convention only; it is the leading _ on the
file that keeps it private. Partials are still compiled, so they must be valid
even though nothing serves them.
Include them from a page with @include("_partials/_header.html.crisp"). The
path is relative to the including file.
Error pages
Error pages are files at the top level of the site with the exact names
400, 404, 500 or error (with .html.crisp or .md.crisp). See
Sessions and forms for the lookup rules.
Static assets and Sass
Files in the assets/ folder (or the folder named by assets in
crisptastic.toml or --assets) are served as static files.
- They are served from the site root, not from
/assets/.assets/logo.svgis/logo.svg;assets/img/a.pngis/img/a.png. - A route beats an asset at the same URL if the route is static. An asset
beats a route that has
[param]segments. - A request for
/site.cssis answered by compilingassets/site.scss(or.sass) if there is no realsite.css. Files whose name starts with_are Sass partials and are never emitted as CSS. - Sass is compiled by an embedded compiler. Extra search folders can be given
with
sass_load_pathsor--sass-load-path. - All Sass is compiled at startup, so a Sass error stops
devfrom starting. After startup, a Sass error keeps serving the last good CSS and prints a warning. - Content types come from the file extension:
css js mjs json webmanifest xml csv map svg png jpg jpeg gif webp avif ico woff woff2 ttf otf pdf mp3 mp4 webm wasm html txt<code>. Everything else is</code>application/octet-stream. - Nothing sets caching headers on assets.
- Raw
.scsssources are downloadable (for example/site.scss), as are any other files in the assets folder. Do not put anything private there.
In production, assets are not served from the host at all by default. See Deploying.
Includes between formats
An HTML page can include a Markdown partial, which is the usual way to write long prose:
@include("_partials/_layout-open.html.crisp")
@include("_partials/_about-body.md.crisp")
@include("_partials/_layout-close.html.crisp")
Details and restrictions are in Pages and templating.
Checking a site without running it
crisptastic check ./my-site # compile; prints "ok: 5 routes, 3 partials"
crisptastic routes ./my-site # list every route pattern
routes prints dynamic segments as :name. Error pages and partials are not
listed. If a route you expected is missing from the output, it is probably a
partial (file name starts with _).