本文へ移動
cccskills
無料GitHub で公開

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 (v2 array shape), links export, createCookieSessionStorage, commitSession, destroySession, requireUserId, remix-utils/csrf, remix-auth.

インストール方法を見る

含まれるファイル(5)

  • SKILL.md8.9 KB
  • references/auth-csrf.md9.4 KB
  • references/links.md4.9 KB
  • references/meta-v2.md6.8 KB
  • references/sessions.md7.5 KB

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<typeof 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<SessionData, 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

<Meta /> and <Links /> must live inside <head> in root.tsx; <ScrollRestoration />, <Scripts />, and <LiveReload /> go at the end of <body>. 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 (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

<Meta /> and <Links /> aggregate differently. <Links /> 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. <Meta /> 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<
  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<string> {
  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 <Form> 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 <head> 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 <Link to="/logout"> pointing at a loader is CSRF-able via <img src="/logout">. Use <Form 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 <Links />.
  • 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

Concernv1v2
meta return shapeObject { title, description }Array [{ title }, { name, content }]
Parent meta mergeAuto-merged (last-write-wins per key)No merge; last matching route only
meta argument for parent dataparentsDatamatches (flatMap manually)
OG tags{ "og:title": "..." } shorthand{ property: "og:title", content: "..." }
Migration flagv2_meta: true future flagN/A (v2 default)

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when you need to mine a conversation, session transcript, or design discussion for architectural decisions before writing ADRs. Identifies problem-solution pairs, trade-off debates, technology choices, and explicit "[ADR]" tags. Triggers on "what decisions did we make", "extract decisions from this chat", "find the choices in our discussion", or "summarize architectural decisions". Also useful after long planning sessions to capture decisions that were made implicitly. Does NOT write ADR documents — use adr-writing or write-adr for that.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when writing or formatting an ADR document using the MADR template, applying Definition of Done (E.C.A.D.R.) criteria, or verifying ADR completeness. Triggers on "write the ADR", "format as MADR", "check ADR quality", "mark gaps in ADR". Also triggers when a decision has been extracted and needs to become a document. Does NOT extract decisions from conversations (use adr-decision-extraction) or orchestrate the full extract-confirm-write workflow (use write-adr).

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when auditing an agent codebase against the 12-Factor Agents methodology, reviewing LLM-powered system architecture, or assessing agentic app compliance. Triggers on "analyze agent architecture", "12-factor audit", "how compliant is this agent", or "evaluate this LLM app". Also applies when comparing frameworks or planning agent improvements. Not for quick checklists — this performs deep per-factor codebase analysis with file-level evidence.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Vercel AI Elements for workflow UI components. Use when building chat interfaces, displaying tool execution, showing reasoning/thinking, or creating job queues. Triggers on ai-elements, Queue, Confirmation, Tool, Reasoning, Shimmer, Loader, Message, Conversation, PromptInput.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Reviews App Intents code for intent structure, entities, shortcuts, and parameters. Use when reviewing code with import AppIntents, @AppIntent, AppEntity, AppShortcutsProvider, or @Parameter.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

Use when the user wants a cited, structured read of local documents and project knowledge. Triggers on: "analyze these docs", "scan my project for context", "read the docs folder", "summarize what's in .beagle/concepts/", "extract context from docs/", "what's in this folder", "go read everything in X and tell me what's there". Also invoked programmatically by other beagle skills (prfaq-beagle Ignition, brainstorm-beagle reference points, strategy-interview context grounding) via the companion contract. Does NOT trigger on codebase lookups ("find this function", "search the repo"), web research (use web-research), LLM-as-judge evaluation (use llm-judge), or document editing (use humanize-beagle). Produces a written scan plan, parallel-subagent findings, and a cited synthesis report on disk — never inline prose, never unsourced claims.

日本語の概要は準備中です。原文の説明を表示しています。

existential-birds/beagle822026年9月16日 更新

existential-birds のスキルをすべて見る

このスキルの問題を報告する