deepagents-architectur…
Guides architectural decisions for Deep Agents applications. Use when deciding between Deep Agents vs alternatives, choosing backend strategies, designing…
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,
$ npx -y skills add existential-birds/beagle --skill remix-v2-meta-sessions --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/remix-v2-meta-sessionsContext preview
The summary Claude sees to decide when to auto-load this skill.
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,
name: remix-v2-meta-sessions description: 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.
**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] : []),
],
},
});`<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](references/meta-v2.md).
`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](references/sessions.md).
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](references/auth-csrf.md).
**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 `Au
Image: NASA, Public Domain. Source Beagle is an Agent Skills marketplace: framework-aware code review, documentation, testing, architectural analysis, and git workflows for any compatible coding agent.
Repo: existential-birds/beagle
Guides architectural decisions for Deep Agents applications. Use when deciding between Deep Agents vs alternatives, choosing backend strategies, designing…
Reviews Deep Agents code for bugs, anti-patterns, and improvements. Use when reviewing code that uses create_deep_agent, backends, subagents, middleware, or…
Implements agents using Deep Agents. Use when building agents with create_deep_agent, configuring backends, defining subagents, adding middleware, or setting…
Guides architectural decisions for LangGraph applications. Use when deciding between LangGraph vs alternatives, choosing state management strategies, designing…
Reviews LangGraph code for bugs, anti-patterns, and improvements. Use when reviewing code that uses StateGraph, nodes, edges, checkpointing, or other LangGraph…
Implements stateful agent graphs using LangGraph. Use when building graphs, adding nodes/edges, defining state schemas, implementing checkpointing, handling…