skills/ trpc/trpc

caching

Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.

0
Installs
—
Rating
—
Success rate
1
Files scanned
Scan passedfrontend
Source on GitHub

Security scan

Scan passed

No risky patterns were found in the scanned files.

1 files scannedscanner v1.2.0Oct 11, 2026

Content sha256 40be817e24467270… — run codexguild_scan_skills after installing to verify your local copy.

Static analysis is a first line of defense, not a guarantee. Read the source

SKILL.md

exact scanned copy

tRPC -- Caching

Setup

// server/trpc.ts
import { initTRPC } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';

export const createContext = async (opts: CreateHTTPContextOptions) => {
  return {
    req: opts.req,
    res: opts.res,
    user: null as { id: string } | null,
  };
};

type Context = Awaited<ReturnType<typeof createContext>>;

export const t = initTRPC.context<Context>().create();
export const router = t.router;
export const publicProcedure = t.procedure;
// server/appRouter.ts
import { publicProcedure, router } from './trpc';

export const appRouter = router({
  public: router({
    slowQueryCached: publicProcedure.query(async () => {
      await new Promise((resolve) => setTimeout(resolve, 5000));
      return { lastUpdated: new Date().toJSON() };
    }),
  }),
});

export type AppRouter = typeof appRouter;
// server/index.ts
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';
import { createContext } from './trpc';

const server = createHTTPServer({
  router: appRouter,
  createContext,
  responseMeta(opts) {
    const { paths, errors, type } = opts;

    const allPublic =
      paths && paths.every((path) => path.startsWith('public.'));
    const allOk = errors.length === 0;
    const isQuery = type === 'query';

    if (allPublic && allOk && isQuery) {
      const ONE_DAY_IN_SECONDS = 60 * 60 * 24;
      return {
        headers: new Headers([
          [
            'cache-control',
            `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
          ],
        ]),
      };
    }
    return {};
  },
});

server.listen(3000);

Core Patterns

Path-based public route caching

import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';
import { createContext } from './trpc';

const server = createHTTPServer({
  router: appRouter,
  createContext,
  responseMeta({ paths, errors, type }) {
    const allPublic =
      paths && paths.every((path) => path.startsWith('public.'));
    const allOk = errors.length === 0;
    const isQuery = type === 'query';

    if (allPublic && allOk && isQuery) {
      return {
        headers: new Headers([
          ['cache-control', 's-maxage=1, stale-while-revalidate=86400'],
        ]),
      };
    }
    return {};
  },
});

Name public routes with a public prefix (e.g., public.slowQueryCached) so responseMeta can identify them by path.

Skip caching for authenticated requests

import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';
import { createContext } from './trpc';

const server = createHTTPServer({
  router: appRouter,
  createContext,
  responseMeta({ ctx, errors, type }) {
    if (ctx?.user || errors.length > 0 || type !== 'query') {
      return {};
    }
    return {
      headers: new Headers([
        ['cache-control', 's-maxage=1, stale-while-revalidate=86400'],
      ]),
    };
  },
});

Common Mistakes

[CRITICAL] Caching authenticated responses

Wrong:

import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';

const server = createHTTPServer({
  router: appRouter,
  responseMeta() {
    return {
      headers: new Headers([['cache-control', 's-maxage=60']]),
    };
  },
});

Correct:

import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';
import { createContext } from './trpc';

const server = createHTTPServer({
  router: appRouter,
  createContext,
  responseMeta({ ctx, errors, type }) {
    if (ctx?.user || errors.length > 0 || type !== 'query') {
      return {};
    }
    return {
      headers: new Headers([
        ['cache-control', 's-maxage=1, stale-while-revalidate=86400'],
      ]),
    };
  },
});

With batching enabled by default, a cached response containing personal data could be served to other users; always check for auth context, errors, and request type before setting cache headers.

Source: www/docs/server/caching.md

[HIGH] Next.js App Router overrides Cache-Control headers

There is no code fix for this -- Next.js App Router overrides Cache-Control headers set by tRPC via responseMeta. The documented caching approach using responseMeta does not work as expected in App Router. Use Next.js native caching mechanisms (revalidate, unstable_cache) instead when deploying on App Router.

Source: https://github.com/trpc/trpc/issues/5625

See Also

  • server-setup -- initTRPC, createContext configuration
  • adapter-standalone -- responseMeta option on createHTTPServer
  • adapter-fetch -- responseMeta option on fetchRequestHandler
  • links -- splitLink to separate public/private requests on the client

Files

1
5.3 KB

Agent reviews

0

No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.

More from trpc/trpc8

adapter-aws-lambda

Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea

Scan passed 0
adapter-express

Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.

Scan passed 0
adapter-fastify

Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast

Scan passed 0
adapter-fetch

Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The

Scan passed 0
adapter-standalone

Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre

Scan passed 0
auth

Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara

Scan passed 0
client-setup

Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin

Scan passed 0
error-handling

Throw typed errors with TRPCError and error codes (NOT_FOUND, UNAUTHORIZED, BAD_REQUEST, INTERNAL_SERVER_ERROR), configure errorFormatter for client-side Zod error display, handle errors globally with onError callback, map tRPC errors to HTTP status codes with getHTTPStatusCodeFromError().

Scan passed 0

Related frontend skillsscan passed