rembrembdocs

The below examples uses Vercel's edge caching to serve data to your users as fast as possible.

If you turn on SSR in your app, you might discover that your app loads slowly on, for instance, Vercel, but you can actually statically render your whole app without using SSG; read this Twitter thread for more insights.

utils/trpc.tsx

tsx

import { httpBatchLink } from '@trpc/client';

import { createTRPCNext } from '@trpc/next';

import type { AppRouter } from '../server/routers/_app';

export const trpc = createTRPCNext<AppRouter>({

config(config) {

`if (typeof window !== 'undefined') {`

  `return {`

    `links: [`

      `httpBatchLink({`

        `url: '/api/trpc',`

      `}),`

    `],`

  `};`

`}`

`const url = process.env.VERCEL_URL`

  `` ? `https://${process.env.VERCEL_URL}/api/trpc` ``

  `: 'http://localhost:3000/api/trpc';`

`return {`

  `links: {`

    `http: httpBatchLink({`

      `url,`

    `}),`

  `},`

`};`

},

ssr: true,

responseMeta(opts) {

`const { clientErrors } = opts;`

`if (clientErrors.length) {`

  `// propagate http first error from API calls`

  `return {`

    `status: clientErrors[0].data?.httpStatus ?? 500,`

  `};`

`}`

`// cache request for 1 day + revalidate once every second`

`const ONE_DAY_IN_SECONDS = 60 * 60 * 24;`

`return {`

  `headers: {`

    ``'cache-control': `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,``

  `},`

`};`

},

});

Since all queries are normal HTTP GETs, we can use normal HTTP headers to cache responses, make the responses snappy, give your database a rest, and easily scale your API to gazillions of users.

server.ts

tsx

import { initTRPC } from '@trpc/server';

import * as trpcNext from '@trpc/server/adapters/next';

export const createContext = async ({

req,

res,

}: trpcNext.CreateNextContextOptions) => {

return {

`req,`

`res,`

`prisma,`

};

};

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

export const t = initTRPC.context<Context>().create();

const waitFor = async (ms: number) =>

new Promise((resolve) => setTimeout(resolve, ms));

export const appRouter = t.router({

public: t.router({

`slowQueryCached: t.procedure.query(async (opts) => {`

  `await waitFor(5000); // wait for 5s`

  `return {`

    `lastUpdated: new Date().toJSON(),`

  `};`

`}),`

}),

});

// Exporting type _type_ AppRouter only exposes types that can be used for inference

// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export

export type AppRouter = typeof appRouter;

// export API handler

export default trpcNext.createNextApiHandler({

router: appRouter,

createContext,

responseMeta(opts) {

`const { ctx, paths, errors, type } = opts;`

``// assuming you have all your public routes with the keyword `public` in them``

`const allPublic = paths && paths.every((path) => path.includes('public'));`

`// checking that no procedures errored`

`const allOk = errors.length === 0;`

`// checking we're doing a query request`

`const isQuery = type === 'query';`

`if (ctx?.res && allPublic && allOk && isQuery) {`

  `// cache request for 1 day + revalidate once every second`

  `const ONE_DAY_IN_SECONDS = 60 * 60 * 24;`

  `return {`

    `headers: {`

      ``'cache-control': `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,``

    `},`

  `};`

`}`

`return {};`

},

});