React Router v7 has been released. View the docs
Cookies
On this page

Cookies

A cookie is a small piece of information that your server sends someone in a HTTP response that their browser will send back on subsequent requests. This technique is a fundamental building block of many interactive websites that adds state so you can build authentication (see sessions), shopping carts, user preferences, and many other features that require remembering who is "logged in".

Remix's Cookie interface provides a logical, reusable container for cookie metadata.

Using cookies

While you may create these cookies manually, it is more common to use a session storage.

In Remix, you will typically work with cookies in your loader and/or action functions (see mutations), since those are the places where you need to read and write data.

Let's say you have a banner on your e-commerce site that prompts users to check out the items you currently have on sale. The banner spans the top of your homepage, and includes a button on the side that allows the user to dismiss the banner so they don't see it for at least another week.

First, create a cookie:

import { createCookie } from "@remix-run/node"; // or cloudflare/deno

export const userPrefs = createCookie("user-prefs", {
  maxAge: 604_800, // one week
});

Then, you can import the cookie and use it in your loader and/or action. The loader in this case just checks the value of the user preference so you can use it in your component for deciding whether to render the banner. When the button is clicked, the <form> calls the action on the server and reloads the page without the banner.

Note: We recommend (for now) that you create all the cookies your app needs in a *.server.ts file and import them into your route modules. This allows the Remix compiler to correctly prune these imports out of the browser build where they are not needed. We hope to eventually remove this caveat.

import type {
  ActionFunctionArgs,
  LoaderFunctionArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import {
  useLoaderData,
  Link,
  Form,
} from "@remix-run/react";

import { userPrefs } from "~/cookies.server";

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const cookieHeader = request.headers.get("Cookie");
  const cookie =
    (await userPrefs.parse(cookieHeader)) || {};
  return json({ showBanner: cookie.showBanner });
}

export async function action({
  request,
}: ActionFunctionArgs) {
  const cookieHeader = request.headers.get("Cookie");
  const cookie =
    (await userPrefs.parse(cookieHeader)) || {};
  const bodyParams = await request.formData();

  if (bodyParams.get("bannerVisibility") === "hidden") {
    cookie.showBanner = false;
  }

  return redirect("/", {
    headers: {
      "Set-Cookie": await userPrefs.serialize(cookie),
    },
  });
}

export default function Home() {
  const { showBanner } = useLoaderData<typeof loader>();

  return (
    <div>
      {showBanner ? (
        <div>
          <Link to="/sale">Don't miss our sale!</Link>
          <Form method="post">
            <input
              type="hidden"
              name="bannerVisibility"
              value="hidden"
            />
            <button type="submit">Hide</button>
          </Form>
        </div>
      ) : null}
      <h1>Welcome!</h1>
    </div>
  );
}

Cookies have several attributes that control when they expire, how they are accessed, and where they are sent. Any of these attributes may be specified either in createCookie(name, options), or during serialize() when the Set-Cookie header is generated.

const cookie = createCookie("user-prefs", {
  // These are defaults for this cookie.
  path: "/",
  sameSite: "lax",
  httpOnly: true,
  secure: true,
  expires: new Date(Date.now() + 60_000),
  maxAge: 60,
});

// You can either use the defaults:
cookie.serialize(userPrefs);

// Or override individual ones as needed:
cookie.serialize(userPrefs, { sameSite: "strict" });

Please read more info about these attributes to get a better understanding of what they do.

Signing cookies

It is possible to sign a cookie to automatically verify its contents when it is received. Since it's relatively easy to spoof HTTP headers, this is a good idea for any information that you do not want someone to be able to fake, like authentication information (see sessions).

To sign a cookie, provide one or more secrets when you first create the cookie:

const cookie = createCookie("user-prefs", {
  secrets: ["s3cret1"],
});

Cookies that have one or more secrets will be stored and verified in a way that ensures the cookie's integrity.

Secrets may be rotated by adding new secrets to the front of the secrets array. Cookies that have been signed with old secrets will still be decoded successfully in cookie.parse(), and the newest secret (the first one in the array) will always be used to sign outgoing cookies created in cookie.serialize().

export const cookie = createCookie("user-prefs", {
  secrets: ["n3wsecr3t", "olds3cret"],
});
import { cookie } from "~/cookies.server";

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const oldCookie = request.headers.get("Cookie");
  // oldCookie may have been signed with "olds3cret", but still parses ok
  const value = await cookie.parse(oldCookie);

  new Response("...", {
    headers: {
      // Set-Cookie is signed with "n3wsecr3t"
      "Set-Cookie": await cookie.serialize(value),
    },
  });
}

createCookie

Creates a logical container for managing a browser cookie from the server.

import { createCookie } from "@remix-run/node"; // or cloudflare/deno

const cookie = createCookie("cookie-name", {
  // all of these are optional defaults that can be overridden at runtime
  expires: new Date(Date.now() + 60_000),
  httpOnly: true,
  maxAge: 60,
  path: "/",
  sameSite: "lax",
  secrets: ["s3cret1"],
  secure: true,
});

To learn more about each attribute, please see the MDN Set-Cookie docs.

isCookie

Returns true if an object is a Remix cookie container.

import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true

A cookie container is returned from createCookie and has a handful of properties and methods.

const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.

cookie.name

The name of the cookie, used in Cookie and Set-Cookie HTTP headers.

cookie.parse()

Extracts and returns the value of this cookie in a given Cookie header.

const value = await cookie.parse(
  request.headers.get("Cookie")
);

cookie.serialize()

Serializes a value and combines it with this cookie's options to create a Set-Cookie header, suitable for use in an outgoing Response.

new Response("...", {
  headers: {
    "Set-Cookie": await cookie.serialize({
      showBanner: true,
    }),
  },
});

cookie.isSigned

Will be true if the cookie uses any secrets, false otherwise.

let cookie = createCookie("user-prefs");
console.log(cookie.isSigned); // false

cookie = createCookie("user-prefs", {
  secrets: ["soopersekrit"],
});
console.log(cookie.isSigned); // true

cookie.expires

The Date on which this cookie expires. Note that if a cookie has both maxAge and expires, this value will be the date at the current time plus the maxAge value since Max-Age takes precedence over Expires.

const cookie = createCookie("user-prefs", {
  expires: new Date("2021-01-01"),
});

console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"
Docs and examples licensed under MIT