Dynamic Gatsby Routes on Cloudflare Pages
Tags: gatsby, cloudflare, jamstack
Published: 2021-10-06 10:28:35

In this post, I'll be talking about getting dynamic routes – or more specifically the rewrites for dynamic routes – to work with Cloudflare Pages deployments.

[Cloudflare Pages](https://pages.cloudflare.com) is still quite new and thus not as feature rich as other longer established JAMstack platforms. But what it lacks in features, it makes up in speed, which is why I desperately want to move some of my sites to it.

As an example in this post I'll be using an actual route I am using in one of my shops. It is a dynamic route that allows my customers to check the status of their order. The route has the following format:

```
/status/:orderId/:token
```

It contains the id of the order and a order-specific secret token to restrict access to this page to the ones who know the URL (only the customer). This page can obviously not be generated during built-time of the site. It instead has to fetch the current order state on the client-side based on the order id and token from the URL.

Since the shop in question is built in Gatsby, I'll be using actual code examples from Gatsby. But the approaches shown in this post can also be applied to (exported) Next.js and other static sites. In Gatsby, I am creating the dynamic route as follows:

```js
createPage({
  path: '/status/:orderId/:token',
  matchPath: '/status/:orderId/:token',
  component: path.resolve('./src/templates/status.tsx'),
})
```

This creates the static `/status/:orderId/:token.html` when the site is built. We now have to make sure that all URLs that match `/status/:orderId/:token` (e.g. `/status/1234/EvEf_HAp7oyb194ZekX39bIcwueV1`) are rewritten to `/status/:orderId/:token.html`. We need a rewrite and not a redirect, because a redirect would change the URL in the browser's address bar and would also lose the actual order id and the token. While Gatsby would still be able to figure out that it has to load `/status/:orderId/:token/index.html` if not have the rewrite, it'd first show a `404` before doing a client-side redirect to the correct page. This is a bad UX and needs to be avoided.

On Netlify, I am simply using a rewrite in the `_redirects` file right now:

```
/status/*  /status/:orderId/:token/index.html  200
```

The status of `200` makes sure that this is a rewrite and not a redirect. While Cloudflare Pages also [supports `_redirects` files](https://developers.cloudflare.com/pages/platform/redirects), it only does for redirects (`301` and `302`) for now. We thus need another solution.

## Option 0: Pages Functions

*Option added to the post on the 2021-11-27.*

Cloudflare [released](https://blog.cloudflare.com/cloudflare-pages-goes-full-stack/) [Pages Functions](https://developers.cloudflare.com/pages/platform/functions) since this post was first created. Pages Functions are now the recommended approach. It is basically the same as _Option 2: Cloudflare Worker_ just that it integrates nicely into your existing repository and doesn't require maintaining a separate worker.

The function for the rewrite is as simple as creating `/functions/status/[order]/[token].ts`. With the following content:

```typescript
export function onRequestGet({ env, request }: EventContext<Env, string, unknown>) {
  return env.ASSETS.fetch(
    new Request(
      new URL('/status/:orderId/:token/', request.url).toString(),
      request
    )
  )
}

interface Env {
  ASSETS: Fetcher
}
```

Make sure that the `/functions/` directory is at the root of your project (the root you specify in the Pages build settings). You don't have to transpire the function from TypeScript to JavaScript, Pages will automatically take care of it.

## Option 1: Transform Rule

The first possible solution is to use a transform rule. In your Cloudflare Dashboard, select your site, go to _Rules_ and create a new _Rewrite URL_ transform rule. Setup the rule as shown in the screenshot below.

![Cloudflare Transform Rule](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6vovh02z5foyrjfqb0r.png)

Make sure to use the `matches regex` operator to match from the beginning of the URI path. Don't use a `contains` operator because we don't want to rewrite the `page-data.json` URL from Gatsby which is located at `/page-data/status/:orderId/:token/page-data.json` in the example.

Also make sure to rewrite the URL to `/status/:orderId/:token/`, and not directly to `/status/:orderId/:token/index.html`, because Cloudflare would return a redirect to `/status/:orderId/:token/index` in the latter case (and a rewrite to `/status/:orderId/:token/` if you try to rewrite it to `/status/:orderId/:token/index`).

If you are still on the _Free_ plan, you don't have access to the `matches regex` operator. In this case, you can fall back to option 2.

## Option 2: Cloudflare Worker

As a second solution, you can deploy a very simple [Cloudflare Worker](https://workers.cloudflare.com). [The docs](https://developers.cloudflare.com/workers/) will get you started on how to create and deploy a worker, so I'll only show the relevant pieces here.

The worker itself is as simple as:

```js
addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(
      new Request(
        new URL("/status/:orderId/:token/", event.request.url).toString(),
        event.request
      )
    )
  );
});
```

It just takes the request, modifies its URL, and executes it. The worker doesn't check if the incoming URL starts with `/status`, since this can be done by deploying the worker to the corresponding route instead. Just add the `route` setting in your `wrangler.toml` accordingly:

```
route = "example.org/status/*"
```

## Drawbacks?

~~What I don't like about both solutions is that they are not tied to the repository of my site. I prefer that each commit in my repo is a self-contained deployment, i.e., I can always check out a specific commit, run one deployment command and everything works as it was supposed to at this commit.~~

~~However, I could probably achieve this with both options. For _Option 1_ I'd have to add calls to Cloudflare's API to ensure that the transform rule exists as intended for the commit, and for _Option 2_ I could add the worker to the repository and update it as part of the site's deployment.~~

~~Another drawback is that both options require your domain to go through Cloudflare - but if you are using Cloudflare Pages, chances are good that it already does.~~

Fixed with _Option 0_ that was added on the 2021-11-27.

## Can I move my shop to Cloudflare Pages yet?

No, unfortunately. I am doing i18n on built-time and thus have different deployments (and domains) for each language I support. Cloudflare Pages [does not support multiple projects per repository yet](https://developers.cloudflare.com/pages/platform/known-issues).