React Router v7 已發布。 查看文件
Cookies
本頁內容

Cookies

Cookie 是一小段資訊,您的伺服器在 HTTP 回應中傳送給使用者,使用者的瀏覽器會在後續請求中將其傳回。這項技術是許多互動式網站的基本構建區塊,它會新增狀態,因此您可以建立身份驗證(請參閱sessions)、購物車、使用者偏好設定以及許多其他需要記住誰「已登入」的功能。

Remix 的 Cookie 介面為 Cookie 元資料提供了一個邏輯、可重複使用的容器。

使用 Cookie

雖然您可以手動建立這些 Cookie,但更常見的是使用session 儲存

在 Remix 中,您通常會在 loader 和/或 action 函式中使用 Cookie(請參閱mutations),因為這些是您需要讀取和寫入資料的地方。

假設您的電子商務網站上有一個橫幅,提示使用者查看您目前正在促銷的商品。該橫幅會橫跨您的首頁頂部,並在側邊包含一個按鈕,讓使用者可以關閉該橫幅,以便他們至少在另一週內不會再看到它。

首先,建立一個 Cookie

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

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

然後,您可以 import 該 Cookie 並在您的 loader 和/或 action 中使用它。在此情況下,loader 只會檢查使用者偏好設定的值,因此您可以在元件中使用它來決定是否要呈現橫幅。當按鈕被按下時,<form> 會在伺服器上呼叫 action 並重新載入沒有橫幅的頁面。

注意:我們建議(目前)您將應用程式所需的所有 Cookie 都建立在 *.server.ts 檔案中,並將它們 import 到您的路由模組中。這可讓 Remix 編譯器正確地將這些 import 從不需要的瀏覽器建置中移除。我們希望最終可以刪除這個警告。

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>
  );
}

Cookie 有幾個屬性,用於控制它們的到期時間、存取方式以及傳送位置。這些屬性中的任何一個都可以在 createCookie(name, options) 中或在產生 Set-Cookie 標頭時的 serialize() 期間指定。

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" });

請閱讀更多關於這些屬性的資訊,以更了解它們的作用。

簽署 Cookie

可以簽署 Cookie 以在收到時自動驗證其內容。由於偽造 HTTP 標頭相對容易,因此對於任何您不希望他人偽造的資訊(例如身份驗證資訊(請參閱sessions))來說,這是一個好主意。

若要簽署 Cookie,請在首次建立 Cookie 時提供一或多個 secrets

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

具有一或多個 secrets 的 Cookie 將以確保 Cookie 完整性的方式儲存和驗證。

可以透過將新的 secret 新增到 secrets 陣列的最前面來輪換 secret。使用舊 secret 簽署的 Cookie 仍然可以在 cookie.parse() 中成功解碼,而最新的 secret(陣列中的第一個)將始終用於簽署在 cookie.serialize() 中建立的傳出 Cookie。

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

建立一個邏輯容器,用於從伺服器管理瀏覽器 Cookie。

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,
});

若要了解每個屬性的詳細資訊,請參閱 MDN Set-Cookie 文件

isCookie

如果物件是 Remix Cookie 容器,則傳回 true

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

Cookie 容器是從 createCookie 傳回,並具有一些屬性和方法。

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

cookie.name

Cookie 的名稱,用於 CookieSet-Cookie HTTP 標頭中。

cookie.parse()

在指定的 Cookie 標頭中提取並傳回此 Cookie 的值。

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

cookie.serialize()

序列化一個值,並將其與此 Cookie 的選項結合,以建立一個 Set-Cookie 標頭,適用於傳出的 Response 中使用。

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

cookie.isSigned

如果 Cookie 使用任何 secrets,則為 true,否則為 false

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

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

cookie.expires

此 Cookie 過期的 Date。請注意,如果 Cookie 同時具有 maxAgeexpires,則此值將是目前時間加上 maxAge 值後的日期,因為 Max-Age 的優先順序高於 Expires

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

console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"
文件和範例以以下授權 MIT