Skip to content
Coritan Docs

Host a static site or a hybrid site

Deploy a site built to plain files that the edge serves itself, set its redirects and headers, and add server routes with a hybrid site.

View as Markdown

A static site is a site whose build writes plain files, such as HTML, CSS, JavaScript and images, to one directory. App Deployment serves those files from the edge itself: there is no instance and no server to size, and the site answers from every location. A hybrid site adds server routes: the edge serves the files the build wrote, and sends every other request to the site's instances, which run where you choose.

A static site counts as one of your plan's websites, and costs nothing within the plan's allowances (How App Deployment is billed). A hybrid site's instances are billed like any other instance.

  • Keep the site in a git repository whose build writes its files to one directory, such as dist, build or out (Deploy from a git repository). Once the site is deployed, you can also upload a directory with coritan deploy (Use the Coritan CLI).
  • Order it from the dashboard's New deployment, when its order page offers What to deploy. Without it, static sites and hybrid sites are not open yet, and the API answers 404 for them too.
  1. In the dashboard, go to App Deployment and select New deployment. Under What to deploy, select Static site, or Starter to begin from a starter such as Astro or Vite.
  2. Under Repository, choose From GitHub and select the repository, or choose Paste an address and paste its https address.
  3. Under Build and run, check the Build command and the Output directory the build writes the files to. Left blank, the output directory is the framework's own (Frameworks we build), or we look for dist, build, out, public or _site. A static site has no size or locations to choose.
  4. Under Networking, keep the Deployment name or type your own, and add any Custom domains. Under Git, check the Production branch.
  5. Select the button under the summary. A static site on its own has nothing to pay when you order it (Place the order).
  6. If you pasted the address of a repository on GitHub, connect GitHub on the deployment's Git tab once it exists, so that each push builds (Connect GitHub).

With the API, write the order's body, as With the API shows: "kind": "static", the repository in source.git, and the build settings in build, with the output directory the build writes the files to in output_dir. To begin from a starter, put its name in source.starter instead (Deploy from a git repository). Send it to POST /checkout/quote to see what it costs, then to POST /services/order with one of the plan's prices as pricing_id. A static site is priced at nothing on either an hourly or a monthly price, and an hourly price needs a minimum total of deposits on your account (How hourly billing works).

Each push to the production branch builds a release, and the edge serves its files once they are all uploaded. The site answers at its platform address, such as web-shop.apps.example.net, and at your own domains (Add a domain and protection).

To run server code as well, such as the server routes of Astro, Nuxt or Next.js, order a hybrid site instead: select Git repository, then Hybrid under How it is served ("kind": "hybrid" with the API), and choose the size and the locations as for any other code (Choose locations and scaling).

How the edge answers a request

Section titled How the edge answers a request

For each request, the edge tries these in order and stops at the first that answers:

  1. The redirects, whether or not a file exists at that path.
  2. The canonical address, when clean URLs or a trailing slash rule is on: a 308 to the address of a file the output has, keeping the query.
  3. The file: the path itself, then path/index.html, then path.html.
  4. The rewrites, only where no file is, so a fallback such as /* /index.html 200 leaves your assets alone.
  5. For a single page app, /index.html. Otherwise the not found page with the status 404 (/404.html unless you name another), or a plain 404. A hybrid site sends the request to its instances instead.

Header rules then apply to every answer. The edge never serves /_redirects or /_headers.

Configure the site with coritan.json

Section titled Configure the site with coritan.json

Put a coritan.json in the output directory. It takes the keys vercel.json uses for the same things, in either spelling:

spa
true serves /index.html for any path that has no file. The output needs an /index.html.
not_found (or notFound)
The page to serve with 404, such as /errors/missing.html.
clean_urls (or cleanUrls)
true serves /about.html at /about, and redirects /about.html there.
trailing_slash (or trailingSlash)
true adds a trailing slash to every address, false removes it.
redirects
Each with a source, a destination, and either statusCode or permanent (true answers 308, false answers 307).
rewrites
Each with a source and a destination, a file of the output to serve in its place.
headers
Each with a source and its headers, a list of key and value.
JSON
{
  "cleanUrls": true,
  "redirects": [
    {"source": "/blog/:slug", "destination": "/articles/:slug", "permanent": true}
  ],
  "rewrites": [
    {"source": "/app/:path*", "destination": "/app/index.html"}
  ],
  "headers": [
    {"source": "/(.*)", "headers": [{"key": "X-Frame-Options", "value": "DENY"}]}
  ]
}

A source matches its path exactly, except for placeholders: :name matches one part of the path, :name* the rest of it (or nothing), :name+ the rest of it (at least one character), * the rest as :splat, and (.*) a group the destination names as $1. A mistake in coritan.json fails the build, and the build log names it.

Redirects in a _redirects file

Section titled Redirects in a _redirects file

A _redirects file in the output directory works as it does on Cloudflare Pages: one rule a line, the source, the destination and an optional status.

Text
/old-page   /new-page         301
/docs/*     /guides/:splat
/app/*      /app/index.html   200

A rule without a status redirects with 302. 301, 302, 303, 307 and 308 redirect whether or not a file exists at the source. 200 serves the destination in place, and 404 serves it as the not found page, both only where no file is. A line the edge cannot take, such as a rule for another domain or one with conditions, is skipped, and the build log lists it as a warning. The rules in coritan.json come first, then those in _redirects, and the first rule that matches wins.

A _headers file sets response headers by path: a path on its own line, then each header on an indented line below it. ! Name removes a header an earlier rule set.

Text
/*
  X-Frame-Options: DENY
  Referrer-Policy: strict-origin-when-cross-origin
/embed/*
  ! X-Frame-Options

Every rule that matches applies, in order. The first to set a header replaces the value the edge would send, and a later rule adds its value after a comma. Headers that frame the answer, such as Content-Length, Content-Encoding and ETag, cannot be set or removed.

A file whose name carries its content hash, such as app.3f9a1c.js, or anything under /_next/static/ or /_app/immutable/, is sent with Cache-Control: public, max-age=31536000, immutable. Every other file, and HTML always, is sent with public, max-age=0, must-revalidate, so browsers check for a new release each time. A header rule can set either. Text files from 1 KiB up are compressed with gzip or brotli for the browsers that take them. To turn compression off for a path, send Cache-Control: no-transform from _headers.

  • A file can be up to 25 MiB, and a release up to 2 GiB in up to 20,000 files.
  • coritan.json, _redirects and _headers can each be up to 256 KiB.
  • A release can have up to 2,100 redirects and rewrites together, and up to 100 header rules, each of up to 2,000 characters.
  • A release keeps its files for at least 30 days after it is built, so a static site can roll back to a release of the last 30 days without building it again (Roll back).

The Releases tab shows the release as ready once its files are uploaded, and the site answers with them at every address. The release serving before keeps serving until then.

Every path answers 404
Check the output directory in the build settings: the edge serves only what the build wrote there. For a single page app, set "spa": true in coritan.json, or add /* /index.html 200 to _redirects.
The build failed on coritan.json
The build log names the rule, such as coritan.json redirects[0]: .... Fix the rule and push again.
A rule in _redirects or _headers does nothing
The build log lists every line that was skipped, and why. A redirect in coritan.json that matches first wins.
A page still shows the old version
HTML is sent with max-age=0, so the browser checks each time. If you set a longer Cache-Control on HTML in _headers, visitors keep the old page until it expires.
The order answers 402 with "entitlement": "websites_max"
A static site is one of your plan's websites, beside your DNS zones and web proxies, and your account has as many as the plan allows. Delete one you no longer need, add a websites pack, or choose a plan with more (Add capacity to a service, How account plans work).

A static site is ordered with POST /api/v1/services/order, as in Order any kind, with "kind": "static" in config and no placement. The build settings are in build:

JSON
{
  "kind": "static",
  "name": "docs-site",
  "source": {"git": {"repo_url": "https://github.com/example/docs-site", "branch": "main"}},
  "build": {"preset": "astro", "build_command": "npm run build", "output_dir": "dist"}
}

A hybrid site takes "kind": "hybrid", with placement and runtime as for a web service. Its releases, domains and variables use the same routes as any deployment, under /api/v1/client/deployments/{uuid}.