Cookies

The loader reads request.headers.get("cookie"); the action returns a Response whose set-cookie header threads out to the browser. Subsequent loads see the cookie.

Tip
The POST/Redirect/GET pattern here is essential: if the action returned 200, the browser would navigate to the action's response body instead of reloading the page. The 303 forces a clean GET under the new cookie state.

Cookies the server sees on this request

(no cookies present)

Click "Set", then refresh — you should see demo_session in the cookie list above.

src/app/examples/cookies/page.tsxTSX
// src/app/examples/cookies/page.tsx (loader + action)

export async function loader({ request }: LoaderArgs) {
  const cookies = parseCookies(request.headers.get("cookie") ?? "");
  return { cookies };
}

export async function action({ request }: ActionArgs) {
  const form = await request.formData();
  const op = String(form.get("op") ?? "");

  // 303 POST/Redirect/GET — forces the browser to re-fetch under the
  // new cookie state. Returning 200 here would navigate to the action
  // response body instead of reloading the page.
  if (op === "set") {
    return new Response(null, {
      status: 303,
      headers: {
        "location": "/examples/cookies",
        "set-cookie": "demo_session=abc123; Path=/; Max-Age=300; SameSite=Lax",
      },
    });
  }
  if (op === "clear") {
    return new Response(null, {
      status: 303,
      headers: {
        "location": "/examples/cookies",
        "set-cookie": "demo_session=; Path=/; Max-Age=0; SameSite=Lax",
      },
    });
  }
  return { ok: true };
}