← 返回 Skills 市场
anderskev

Remix V2 Meta Sessions

作者 Kevin Anderson · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ⚠ pending
50
总下载
0
收藏
0
当前安装
1
版本数
在 OpenClaw 中安装
/install remix-v2-meta-sessions
功能描述
Remix v2 meta/SEO, sessions, auth, and CSRF. Use when working with document head, cookie sessions, auth gates, or CSRF protection. Triggers on meta export (v...
使用说明 (SKILL.md)

Remix v2 Meta, Sessions, Auth, and CSRF

Quick Reference

v2 meta returns an array of descriptor objects — NOT the v1 object shape. A v1-style object literal still typechecks in stale codebases but renders no tags at runtime.

// app/routes/posts.$slug.tsx
import type { MetaFunction } from "@remix-run/node";

export const meta: MetaFunction\x3Ctypeof loader> = ({ data }) => {
  if (!data?.post) return [{ title: "Not Found" }];
  return [
    { title: `${data.post.title} | My Blog` },
    { name: "description", content: data.post.excerpt },
    { property: "og:title", content: data.post.title },
    { tagName: "link", rel: "canonical", href: data.post.url },
  ];
};

Cookie session storage with secure defaults and secret rotation:

// app/session.server.ts
import { createCookieSessionStorage } from "@remix-run/node";

type SessionData = { userId: string };
type SessionFlashData = { error: string };

const SESSION_SECRET = process.env.SESSION_SECRET;
if (!SESSION_SECRET) throw new Error("SESSION_SECRET is required");

export const { getSession, commitSession, destroySession } =
  createCookieSessionStorage\x3CSessionData, SessionFlashData>({
    cookie: {
      name: "__session",
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "lax",
      path: "/",
      maxAge: 60 * 60 * 24 * 30,
      secrets: [
        SESSION_SECRET,
        ...(process.env.SESSION_SECRET_OLD ? [process.env.SESSION_SECRET_OLD] : []),
      ],
    },
  });

Document Head: meta and links

\x3CMeta /> and \x3CLinks /> must live inside \x3Chead> in root.tsx; \x3CScrollRestoration />, \x3CScripts />, and \x3CLiveReload /> go at the end of \x3Cbody>. Missing either of these aggregators produces "css doesn't load" or "meta tags missing" with no compile error.

// app/root.tsx
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";

export default function App() {
  return (
    \x3Chtml lang="en">
      \x3Chead>
        \x3Cmeta charSet="utf-8" />
        \x3Cmeta name="viewport" content="width=device-width, initial-scale=1" />
        \x3CMeta />
        \x3CLinks />
      \x3C/head>
      \x3Cbody>
        \x3COutlet />
        \x3CScrollRestoration />
        \x3CScripts />
        \x3CLiveReload />
      \x3C/body>
    \x3C/html>
  );
}

\x3CMeta /> and \x3CLinks /> aggregate differently. \x3CLinks /> walks the entire route match chain and renders every matched route's links export — a stylesheet declared in a leaf route is rendered automatically and unloaded on navigation away. \x3CMeta /> does NOT aggregate; Remix picks the last matching route's meta array only. To inherit from parents in meta, flatMap matches explicitly:

import type { MetaFunction } from "@remix-run/node";
import type { loader as projectLoader } from "./project.$pid";

export const meta: MetaFunction\x3C
  typeof loader,
  { "routes/project.$pid": typeof projectLoader }
> = ({ data, matches }) => {
  const parentMeta = matches.flatMap((m) => m.meta ?? []);
  const project = matches.find((m) => m.id === "routes/project.$pid")?.data;
  return [
    ...parentMeta,
    { title: `${data?.task.name} | ${project?.name}` },
  ];
};

The second generic on MetaFunction (keyed by route id) types matches.find(...).data for parent routes. See references/meta-v2.md.

Sessions

commitSession must be attached as a Set-Cookie header on every mutating response. Remix does NOT auto-commit; calling session.set(...) and returning plain json(data) silently drops the change.

return redirect("/dashboard", {
  headers: { "Set-Cookie": await commitSession(session) },
});

session.flash(key, value) is read-once; the consuming loader must still call commitSession after reading to clear the flash. See references/sessions.md.

Auth: throw redirect from loaders

The canonical pattern is a requireUserId(request) helper that throws redirect() for unauthenticated requests. The thrown response short-circuits the loader; no top-level return is needed.

// app/auth.server.ts
import { redirect } from "@remix-run/node";
import { getSession } from "./session.server";

export async function requireUserId(request: Request): Promise\x3Cstring> {
  const session = await getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");
  if (!userId) {
    const url = new URL(request.url);
    const redirectTo = `${url.pathname}${url.search}`;
    throw redirect(`/login?redirectTo=${encodeURIComponent(redirectTo)}`);
  }
  return userId;
}

Never gate routes inside React components — the protected component still SSRs and ships HTML/loader data to unauthenticated users. See references/auth-csrf.md.

CSRF

Remix has no built-in CSRF protection. Same-origin \x3CForm> posts rely entirely on whatever SameSite value you set on the session cookie. SameSite=Lax blocks cookies on cross-site POST navigations in all current browsers. (Chrome briefly had a 2-minute "Lax+POST" window in 2020 — removed in 2021.) The real Lax-vs-Strict tradeoff is subdomain takeover: with Lax, a compromised subdomain can initiate top-level GET nav with credentials; with Strict, deep-link navigations from external sites lose session. Apps that use SameSite=None for legitimate cross-site needs (OAuth popups, iframe embeds) have no cookie-level CSRF protection at all. Recommend remix-utils/csrf with a dedicated signed cookie — never reuse the session cookie. Manual fetch("/api/x", { method: "POST" }) bypasses AuthenticityTokenInput, so any action that does not call csrf.validate(request) is an attacker entry point.

Gates (decision sequencing)

Answer in order. Pass means the condition is true; pick the API on the same line and stop.

Where does this meta tag live?

  1. Is it site-wide (charset, viewport, default OG image)?
    • Pass → Plain JSX inside \x3Chead> in root.tsx. Avoids the v2 no-merge surprise and prevents duplicate tags. Stop.
    • Fail → Step 2.
  2. Is it route-specific (title, description, canonical, JSON-LD)?
    • Pass → export const meta in the leaf route file; if you need parent values, matches.flatMap((m) => m.meta ?? []). Stop.

Auth check: loader, action, or helper?

  1. Is this a GET (page render) that must be protected?
    • Pass → Call await requireUserId(request) at the top of the loader. Stop.
  2. Is this a POST/PUT/DELETE mutation that must be protected?
    • Pass → Call await requireUserId(request) at the top of the action, AND call await csrf.validate(request). Stop.
  3. Logout?
    • Pass → action only, never loader. A \x3CLink to="/logout"> pointing at a loader is CSRF-able via \x3Cimg src="/logout">. Use \x3CForm method="post" action="/logout">. Stop.

Where does the CSRF token live?

  1. Are you using remix-utils/csrf?
    • Pass → A dedicated createCookie("csrf", { ... }) cookie, separate from the session cookie. The CSRF value is a signed string; the session value is a serialized object — reusing one cookie throws on validate. Stop.
    • Fail → Step 2.
  2. No CSRF library?
    • Pass → Document the threat model; require sameSite: "strict" on the session cookie and verify the Origin header in every action. Prefer adding remix-utils/csrf instead.

Additional Documentation

  • Meta v2: See references/meta-v2.md for descriptor types, parent merging via matches, JSON-LD, and v1→v2 migration pitfalls.
  • Links: See references/links.md for stylesheet, preload, dns-prefetch, and the parent-aggregation behavior of \x3CLinks />.
  • Sessions: See references/sessions.md for createCookieSessionStorage config, commitSession/destroySession patterns, flash messages, and database-backed sessions.
  • Auth and CSRF: See references/auth-csrf.md for requireUserId helpers, login/logout actions, remix-auth, and remix-utils/csrf wiring.

v1 vs v2 Quick Comparison

Concern v1 v2
meta return shape Object { title, description } Array [{ title }, { name, content }]
Parent meta merge Auto-merged (last-write-wins per key) No merge; last matching route only
meta argument for parent data parentsData matches (flatMap manually)
OG tags { "og:title": "..." } shorthand { property: "og:title", content: "..." }
Migration flag v2_meta: true future flag N/A (v2 default)
能力标签
requires-oauth-tokenrequires-sensitive-credentials
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install remix-v2-meta-sessions
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /remix-v2-meta-sessions 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.0.0
remix-v2-meta-sessions 1.0.0 - Initial release providing documentation and best practices for: - Remix v2 meta/SEO configuration using the new array shape - Cookie-based session storage with secret rotation and secure defaults - Correct aggregation and placement of <Meta /> and <Links /> components - Auth gating with session-based user checks and canonical patterns - CSRF protection strategies, including use of remix-utils/csrf - Clear decision guides for meta tag placement, auth checks, and CSRF handling
元数据
Slug remix-v2-meta-sessions
版本 1.0.0
许可证 MIT-0
累计安装 0
当前安装数 0
历史版本数 1
常见问题

Remix V2 Meta Sessions 是什么?

Remix v2 meta/SEO, sessions, auth, and CSRF. Use when working with document head, cookie sessions, auth gates, or CSRF protection. Triggers on meta export (v... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 50 次。

如何安装 Remix V2 Meta Sessions?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install remix-v2-meta-sessions」即可一键安装,无需额外配置。

Remix V2 Meta Sessions 是免费的吗?

是的,Remix V2 Meta Sessions 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

Remix V2 Meta Sessions 支持哪些平台?

Remix V2 Meta Sessions 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 Remix V2 Meta Sessions?

由 Kevin Anderson(@anderskev)开发并维护,当前版本 v1.0.0。

💬 留言讨论