Redirects and Path Rewrites

Stormkit is able to handle the path rewrites and redirects on the load balancer level. In order to make use of this feature, create a `redirects.json` file at root level of your repository. This file will be parsed on each deployment, hence if you change this file previous deployments won't be affected. The syntax is as follows:
[
  {
    "from": "string", // (required): The path. Supports regexp syntax.
    "to": "string", // (required): The destination path.
    "status": "number", // (optional): The HTTP Status Code for redirect. Default is empty.
    "assets": "boolean", // (optional): Whether to apply the redirect/rewrite to any static file that is not an html file. Default is false.
    "hosts": "Array<string>", // (optional): When provided, the redirect rule will apply only when the host name matches.
    "data": "object" // (optional): Fetches a JSON document and fills it into the page the rule rewrites to. See Dynamic pages.
  }
]

Path rewrites

If you omit the status property, or provide a status different than 3xx, Stormkit will not redirect the request but will simply rewrite the path.

[
  {
    "from": "/my-path/*",
    "to": "/my-new-path/$1"
  }
]

In this case, all requests coming to /my-path will be served as if they were coming to /my-new-path.

Proxies

You can also use redirects as a proxy. If your redirect is an absolute URL (starting with http), the request will be proxied.

[
  {
    "from": "/my-path/*",
    "to": "https://example.com/my-new-path/$1",
    "status": 200
  }
]

In this case, all requests coming to /my-path will be proxied to https://example.com/my-new-path/*.

Dynamic pages

A static site sometimes needs one dynamic route: a page per video, product or profile, with the right <title> and Open Graph tags for crawlers and link previews. Add a data loader to a rewrite rule. Stormkit fetches a JSON document for each request and renders the HTML file the rule rewrites to as a template, with that document as its data. Your markup, styles and asset URLs stay in your build, and your API only returns JSON.

[
  {
    "from": "/v/*",
    "to": "/videos.html",
    "data": {
      "url": "https://api.example.com/v/$1.json"
    }
  }
]

A request to /v/abc123 serves /videos.html from your deployment, filled with the document at https://api.example.com/v/abc123.json.

Property Required Description
data.url Yes The JSON document to fetch. Must be https. Wildcard captures from from are available as $1, $2, and so on.
data.passthroughStatus No When true, an error status from the API becomes the page's status code. Default is false: the page is served with 200.

Captures are URL-encoded before they are inserted, so a visitor's path can add path segments to data.url but cannot add a query string or fragment. A path whose capture contains a . or .. segment does not match the rule.

A loader only works on a rewrite to a file in your deployment. It is rejected on a proxy rule (a to starting with http) and on a 3xx redirect.

Templates

The HTML file is a Go html/template. . is the JSON document, and .field reads a field from it:

<title>{{.title | default "Videos"}} · Example</title>
<meta property="og:title" content="{{.title}}" />
<meta property="og:image" content="{{.thumbnail | default "/og.png"}}" />
<meta name="robots" content="{{.robots | default "noindex"}}" />
{{with .owner}}<span>{{.name}}</span>{{end}}
<ul>{{range .tags}}<li>{{.}}</li>{{end}}</ul>
<script id="__sk_data__" type="application/json">{{.}}</script>
  • Values are escaped for where they appear: in text, attributes, URLs and scripts. A javascript: URL from the API is replaced with #ZgotmplZ.
  • {{.}} inside a <script type="application/json"> block renders the whole document as JSON. Read it from your client code to hydrate the page.
  • A missing field renders as an empty string.
  • default applies when a field is missing, null or an empty string. It does not apply to false or 0: {{.public | default true}} renders false when the API returns false. The built-in or treats false and 0 as empty, so prefer default.
  • Use {{with .owner}}{{.name}}{{end}} when an object may be missing. {{.owner.name}} fails when owner is absent, and the visitor gets a 500.
  • if, else, range, eq, and, not and the rest of the template actions are available.

If the file fails to parse, or fails while rendering, the visitor gets a 500 error page that shows the template error, and the error is written to your runtime logs.

When the API fails

Your page is never blocked on the API for long. The fetch times out after 2 seconds and reads at most 1 MB. When the API fails, the document is empty and status holds the API's status code, or 0 when the API could not be reached. Use them to show something else for a missing or withheld record:

{{if .}}
  <h1>{{.title}}</h1>
  <p>By {{.owner.name}}</p>
{{else if eq status 410}}
  <h1>This video is no longer available</h1>
{{else if eq status 404}}
  <h1>Video not found</h1>
{{else}}
  <h1>Something went wrong. Please try again later.</h1>
{{end}}

{{if .}} is false when the document is empty, so the fields inside it, including nested ones like .owner.name, are only read when the API returned data.

  • The API is unreachable, times out or returns invalid JSON: the page is rendered with an empty document and status set to 0.
  • The API returns an error status (e.g. 404): the page is rendered with an empty document, and the error response body is ignored. status holds the error status. With "passthroughStatus": true the visitor gets that status code too, so a deleted record returns a real 404.

Limitations

  • HTML comments are removed from the rendered page.
  • Every {{ in the file is read as template syntax, and text that is not valid template syntax is a template error. To output a literal {{, write {{"{{"}}.

Caching

Stormkit does not cache pages with a loader or their JSON documents: every request calls your API. Stormkit also does not send ETag or Last-Modified for these pages, so browsers and CDNs always get the current data. If an endpoint is expensive, cache it in your API.

Request headers

The request to your API carries the visitor's details, so it can count views and tell people apart from bots and link unfurlers:

Header Value
User-Agent The visitor's User-Agent
X-Forwarded-For The visitor's IP address
X-Real-IP The visitor's IP address
Accept application/json

Identical requests from the same visitor that arrive at the same time share a single call to your API. Requests from different visitors are never combined.

More than one route

Stormkit applies the first rule that matches, and * also matches /. List more specific rules first: here /v/abc/embed would match /v/* if that rule came first.

[
  {
    "from": "/v/*/embed",
    "to": "/embed.html",
    "data": { "url": "https://api.example.com/v/$1.json" }
  },
  {
    "from": "/v/*",
    "to": "/videos.html",
    "data": { "url": "https://api.example.com/v/$1.json" }
  }
]

Hide the template file

The file your rule rewrites to is a normal file in your deployment, so /videos.html can also be opened directly, showing its raw template. Redirect it away. List this rule after the loader rule:

[
  {
    "from": "/v/*",
    "to": "/videos.html",
    "data": { "url": "https://api.example.com/v/$1.json" }
  },
  {
    "from": "/videos(.html)?",
    "to": "/",
    "status": 301
  }
]

Stormkit applies the first rule that matches, so /v/abc123 still rewrites to the template while direct requests to /videos and /videos.html are redirected.

Self-hosted instances

A loader only connects to public addresses. On a self-hosted instance whose API runs on a private network, set STORMKIT_PAGE_DATA_INSECURE=true to allow private addresses and plain http URLs. See Advanced Configuration.

Redirect non-www to www

[
  {
    "from": "stormkit.io",
    "to": "www.stormkit.io",
    "assets": true,
    "status": 301
  }
]

SPA config

[
  {
    "from": "/*",
    "to": "/index.html"
  }
]

The above example will rewrite all requests to index.html. By setting assets false This is useful for single page applications.

Regexp

[
  {
    "from": "/documentation/*/page/*",
    "to": "/docs/$1/$2"
  },
  {
    "from": "/documentation$",
    "to": "/docs"
  }
]

You can use regexp syntax for redirects. The example above creates two redirects.

  1. The first one will redirect /documentation/welcome/page/getting-started to /docs/welcome/getting-started.
  2. The second will redirect /documentation to /docs.

Note the $amp; sign at the end of the string. That sign simply tells to redirect only the path /documentation and not anything that contains /documentation.

Matching host names

[
  {
    "from": "/path",
    "to": "/new-path",
    "hosts": ["example-a.org"]
  },
  {
    "from": "/path",
    "to": "/different-path",
    "hosts": ["example-b.org", "example-c.org"]
  }
]

If you have multiple domains configured for your environment, you can specify for which host name the redirect rule should apply to.

The example above will rewrite the /path to /new-path for example-a.org and to /different-path for example-b.org and example-c.org.

Redirecting API Routes

Please note that if your application contains API routes, paths starting with /api will not be matched. This is to allow /api routes to handle the redirect themselves. If you do not have any API function, this rule does not apply.

You can configure the API routes through the Serverless configuration section.

Custom 404 pages

By Default, when a page is not found, Stormkit will try to serve /404.html or /error.html if any of these files are found in your deployment. You can customize this behaviour as follows:

  1. Go to Environment Config > Redirects
  2. Find the Custom Error File field
  3. Type the file that should be served instead (e.g. /index.html).

This setting will be applied to all of your deployments and take effect instantly. There is no need for a deployment.

Note If you have API routes configured, the custom error file will not be applied to the paths starting with your API Path - which is /api by default.

Note Similarly, if you have serverless side logic, the custom error file will not be applied.

Environment level redirects

You can specify the same rules at an environment level. To do so:

  1. Go to Environment Config > Redirects
  2. Switch Overwrite redirects
  3. Specify the rules from the Redirects Editor and
  4. Click save.

These rules will be applied to all of your deployments and take effect instantly. There is no need for a deployment.

\n```\n\n- Values are escaped for where they appear: in text, attributes, URLs and scripts. A `javascript:` URL from the API is replaced with `#ZgotmplZ`.\n- `{{.}}` inside a `