Aller au contenu principal
Version : 10.x

Mise en cache des réponses

Traduction Bêta Non Officielle

Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →

Les exemples ci-dessous utilisent la mise en cache de périphérie de Vercel pour servir les données à vos utilisateurs le plus rapidement possible.

info

Soyez toujours prudent avec la mise en cache - particulièrement lorsque vous manipulez des informations personnelles.

 
Comme le traitement par lots est activé par défaut, nous recommandons de définir vos en-têtes de cache dans la fonction responseMeta et de vérifier qu'aucun appel concurrent ne contient de données personnelles - ou d'omettre complètement les en-têtes de cache s'il existe un en-tête d'authentification ou un cookie.

 
Vous pouvez également utiliser un splitLink pour séparer vos requêtes publiques de celles qui doivent rester privées et non mises en cache.

Mise en cache d'application

Si vous activez le SSR dans votre application, vous pourriez constater des temps de chargement lents sur Vercel par exemple. Pourtant, vous pouvez effectuer un rendu statique complet de votre application sans utiliser SSG ; consultez ce fil Twitter pour plus d'informations.

Exemple de code

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}`,
},
};
},
});
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}`,
},
};
},
});

Mise en cache des réponses d'API

Comme toutes les requêtes sont des GET HTTP standards, nous pouvons utiliser les en-têtes HTTP classiques pour mettre les réponses en cache, accélérer leur livraison, soulager votre base de données et facilement adapter votre API à des milliards d'utilisateurs.

Utiliser responseMeta pour la mise en cache

En supposant que vous déployez votre API sur une plateforme gérant les en-têtes stale-while-revalidate comme Vercel.

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 {};
},
});
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 {};
},
});