Serverless Remix Sessions with Cloudflare Pages
Tags: cloudflare, remix, javascript
Published: 2022-02-01 16:38:11

---
title: Serverless Remix Sessions with Cloudflare Pages
published: true
description: 
series: SaaS Product
tags: cloudflare, remix, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hj6qt0xuhzooenodwwll.jpg
---

Using sessions with [Remix](https://remix.run/) is a pretty straightforward task. Usually, you put your session data into a cookie and are done with it. But cookies come with some downsides. For example, the client sends them with every request. This makes cookies a lousy place to store vast amounts of data. 

But we're lucky! If we deploy our Remix app onto [Cloudflare Pages](https://pages.cloudflare.com/), we get a globally replicated key-value store to store all our session data!

Workers KV can store all our session data on the backend, and we only need to send a session ID in the cookie to find that data on later requests.

Strangely, the way we access Workers KV on a Cloudflare Worker function is different from a Cloudflare Pages function. Because, why should things work as expected for once?! :D

I got the following error but only found examples online that access KVs via a global variable.

    ReferenceError: KV is not defined.
    Attempted to access binding using global in modules.
    You must use the 2nd `env` parameter passed to exported
    handlers/Durable Object constructors, or `context.env`
    with Pages Functions.

So, in this article, I'll explain how to set up a basic Remix session with KV and Pages.

## Initializing a Remix Project

To start, we create a Remix project with the help of NPX.

    $ npx create-remix@latest

I answered the questions like this:

    ? Where would you like to create your app? example-remix-app
    ? Where do you want to deploy? Choose Remix if you're unsure; it's easy to change deployment targets. Cloudflare Pages
    ? TypeScript or JavaScript? TypeScript
    ? Do you want me to run `npm install`? Yes

But the only meaningful answer here is to use "Cloudflare Pages" as a deployment target.

## Adding a KV Storage to Our Scripts

Inside the `package.json` is a `dev:wrangler` script; we need to extend it with a KV parameter.

```json
  "scripts": {
    "build": "cross-env NODE_ENV=production remix build",
    "dev": "cross-env NODE_ENV=development run-p dev:*",
    "postinstall": "remix setup cloudflare-pages",
    "dev:remix": "remix watch",
    "dev:wrangler": "wrangler pages dev ./public --watch ./build --kv sessionStorage",
    "start": "npm run dev:wrangler"
  },
```

When we run the `dev` script, this will ensure that the local runtime environment [Miniflare](https://miniflare.dev/) will bind a KV with the name `sessionStorage` to our Pages function. 

Later, we can access our KV from `context.env.sessionStorage`.

## Remix and Cloudflare's `context` Object

The next step is to create a session storage. In our case, it will be a Cloudflare KV based one.

And here we're already at the point where things differ between Cloudflare Pages and Workers.

The examples for Cloudflare Workers all use a global KV namespace variable, which doesn't exist. 

So, for our example KV above, we would access a global `sessionStorage` variable. They create the storage before the request gets handled and then export it as a module for all other modules to use. But as explained, this doesn't work here. 

Pages supplies our handler function inside `functions/[[path]].js` with a `context` object that has an `env` attribute. This means the KV reference isn't available before we handle a request.

Now, the problem here is this context object gets picked apart by Remix's `handleRequest` function, which, in turn, is created with the `createPagesFunctionHandler` function.

In the end, we don't get direct access to the `context` object, but only parts of it.

## Creating a Session Storage

To create session storage anyway, we have to hook a callback between the Pages `onRequest` function and our Remix app.

To do so, we can use the `getLoadContext` callback `createPagesFunctionHandler` accepts as a parameter.

Simply update the code inside `functions/[[path]].js` as follows:

```javascript
import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages"
import { createCloudflareKVSessionStorage } from "@remix-run/cloudflare-pages"

import * as build from "../build"

const handleRequest = createPagesFunctionHandler({
  build,
  getLoadContext: (context) => {
    const sessionStorage = createCloudflareKVSessionStorage({
      cookie: {
        name: "SESSION_ID",
        secrets: ["YOUR_COOKIE_SECRET"],
        secure: true,
        sameSite: "strict",
      },
      kv: context.env.sessionStorage,
    })

    return { sessionStorage }
  },
})

export function onRequest(context) {
  return handleRequest(context)
}
```

As we can see, the `getLoadContext` callback receives Cloudflare's `context` object, and we can use it to create our session storage.

## Using the Session

The final question is, where does the object we returned from the callback end up?

Inside the `context` object of your Remix loader and action functions!

So, if you now write a loader, you can look into the session.

I wrote a simple example for an index route inside `app/routes/index.ts`:

```typescript
import { json, LoaderFunction } from "remix"

export const loader: LoaderFunction = async ({ context, request }) => {
  const session = await context.sessionStorage.getSession(
    request.headers.get("Cookie")
  )

  const headers = {}

  if (!session.has("userId")) {
    session.set("userId", `user:${Math.random()}`)
    headers["Set-Cookie"] = await context.sessionStorage.commitSession(session)
  } else {
    console.log(session.get("userId))
  }
  return json(null, { headers })
}
```

The `context` contains our `sessionStorage`, an abstraction around Workers KV. 

This storage knows in which cookie the session ID is stored and uses the session ID to load the corresponding data from the KV.

In the first request, the cookie won't contain a session ID, so that we will end up with an empty session object.

We then use this `session` to check if it has a `userId` and, if not, add one to it.

Then the session gets saved to KV again, and the cookie gets the session ID.

Finally, to ensure our session ID gets sent to the client, we have to return a response with the `Set-Cookie` header.

## Running the Example

To run the example, use the dev script, which calls the updated `dev:wrangler` script, which binds the KV.

    $ npm run dev

After one request, we will see a `SESSION_ID` cookie if we look into our cookies. 

Looking into the log output after the second request, we see the randomly generated `userId`.

## Conclusion

Setting up serverless session handling with Remix and Cloudflare Pages isn't too hard. Things are just a bit more dynamic than with Cloudflare Workers. 

Remix offers a nice abstraction around session handling, and it works seamlessly with serverless KV storage.

Thanks to [maverickdotdev](https://twitter.com/maverickdotdev) for solving the mystery about the `getLoaderContext`!