deepagents-architectur…
Guides architectural decisions for Deep Agents applications. Use when deciding between Deep Agents vs alternatives, choosing backend strategies, designing…
Remix v2 performance, streaming, caching, and server/client boundaries. Use when configuring HTTP caching, server-only modules, hydration safety, or prefetch. Triggers on headers export, Cache-Control, PrefetchPageLinks, Link prefetch, .server.ts, .client.ts, useHydrated,
$ npx -y skills add existential-birds/beagle --skill remix-v2-perf-ssr --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/remix-v2-perf-ssrContext preview
The summary Claude sees to decide when to auto-load this skill.
Remix v2 performance, streaming, caching, and server/client boundaries. Use when configuring HTTP caching, server-only modules, hydration safety, or prefetch. Triggers on headers export, Cache-Control, PrefetchPageLinks, Link prefetch, .server.ts, .client.ts, useHydrated,
name: remix-v2-perf-ssr description: Remix v2 performance, streaming, caching, and server/client boundaries. Use when configuring HTTP caching, server-only modules, hydration safety, or prefetch. Triggers on headers export, Cache-Control, PrefetchPageLinks, Link prefetch, .server.ts, .client.ts, useHydrated, ClientOnly, window.ENV, links preload, useId.
Remix v2 has no built-in image optimizer and no opaque framework cache — it pushes everything to the standard HTTP layer. The performance surface is four pillars: streaming (`defer`/`<Await>`), HTTP caching (`headers` export), prefetching (`<Link prefetch>` and `<PrefetchPageLinks>`), and a hard server/client split (`.server.*` / `.client.*` file conventions).
**`headers` export with SWR (forward loader headers to the document)**:
import type { HeadersFunction, LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
export async function loader({ params }: LoaderFunctionArgs) {
const post = await cms.getPost(params.slug);
return json(post, {
headers: {
"Cache-Control":
"public, max-age=60, s-maxage=3600, stale-while-revalidate=86400",
},
});
}
export const headers: HeadersFunction = ({ loaderHeaders }) => ({
"Cache-Control": loaderHeaders.get("Cache-Control") ?? "no-store",
});**`.server.ts` for server-only modules** — build fails loud if the file leaks into the client graph:
// app/lib/db.server.ts — never bundled into the client
import { PrismaClient } from "@prisma/client";
export const db = new PrismaClient();**`defer()` for slow secondary data**:
import { defer } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";
export async function loader({ params }: LoaderFunctionArgs) {
const product = await db.getProduct(params.id); // critical, awaited
const reviews = db.getReviews(params.id); // slow, not awaited
return defer({ product, reviews });
}
export default function Product() {
const { product, reviews } = useLoaderData<typeof loader>();
return (
<>
<ProductHeader product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Await resolve={reviews} errorElement={<ReviewsError />}>
{(r) => <ReviewList reviews={r} />}
</Await>
</Suspense>
</>
);
}Every promise passed to `defer` must be created **before** any `await` in the loader, otherwise the loader still blocks on the slow call and streaming gains nothing. Always pair `<Await>` with `errorElement` — without it, a rejected deferred promise bubbles to the route's `ErrorBoundary` and tears down the whole route, defeating the streaming benefit.
See [references/streaming.md](references/streaming.md) for full coverage.
`max-age` controls browser cache; `s-maxage` controls shared/CDN cache and overrides `max-age` at the CDN; `stale-while-revalidate` lets the CDN serve stale content while it refreshes in the background. Two cache scopes exist per route: the **document** response (controlled by the `headers` export) and the **data** request (the `?_data=` JSON request fired on client-side navigation — controlled by the loader's response headers). They can — and often should — carry different policies.
**Parent/child merge is "deepest route wins"**: only the deepest matched route's `headers` runs by default. If a child route has no `headers` export, Remix walks up to the nearest parent that does. The safest rule: define `headers` only on leaf routes, never on layouts that wrap personalized children. Otherwise an aggressive parent policy silently caches per-user HTML at the CDN.
When merging in a child, pick the **smaller** `max-age` — never widen a parent's caching policy from a child:
export const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {
const loader = parseCacheControl(loaderHeaders.get("Cache-Control"));
const parent = parseCacheControl(parentHeaders.get("Cache-Control"));
const maxAge = Math.min(loader["max-age"] ?? 0, parent["max-age"] ?? 0);
return { "Cache-Control": `private, max-age=${maxAge}` };
};See [references/headers-caching.md](references/headers-caching.md).
The compiler strips `loader`, `action`, and `headers` exports from client bundles along with the dependencies used **inside them** — but only if those dependencies have no module side effects. A top-level `new PrismaClient()`, a `console.log`, an `initializeApp` call all defeat tree-shaking. Rule: any module that imports `node:fs`, `prisma`, `bcrypt`, `jsonwebtoken`, or reads `process.env` should be named `*.server.ts` (or live under `app/.server/` — directory form requires the Remix Vite plugin; Classic Compiler supports only the filename suffix). Build fails loud if it reaches the client graph — silent leaks are eliminated.
Public env vars reach the browser via a root-loader `window.ENV` pattern. Never return raw `process.env` from a loader. See [references/server-client-split.md](references/server-client-split.md).
v2 added optional `clientLoader` / `clientAction` exports that run in the browser alongside (or instead of) the server `loader`/`action`. By default `clientLoader` does **NOT** run on initial hydration — the server `loader` SSRs the page, and `clientLoader` only fires on subsequent client navigations. Opt in to first-render execution with `clientLoader.hydrate = true` and export a `HydrateFallback` component to render while it executes:
import type { ClientLoaderFunctionArgs } from "@remix-run/react";
export async function loader() {
return json({ /* SSR data */ });
}
export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
const cached = clientCache.get();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…