Rendu côté serveur
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 →
Pour activer le SSR, définissez simplement ssr: true dans le callback de configuration de votre createTRPCNext.
Lorsque vous activez le SSR, tRPC utilise getInitialProps pour précharger toutes les requêtes côté serveur. Cela engendre des problèmes comme celui-ci lorsque vous utilisez getServerSideProps, et la résolution dépasse notre contrôle.
Alternativement, vous pouvez laisser le SSR désactivé (par défaut) et utiliser les Server-Side Helpers pour précharger les requêtes dans getStaticProps ou getServerSideProps.
Pour exécuter correctement les requêtes lors de l'étape de rendu côté serveur, nous devons ajouter une logique supplémentaire dans notre config. De plus, envisagez la Response Caching.
utils/trpc.tstsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from './api/trpc/[trpc]';export consttrpc =createTRPCNext <AppRouter >({ssr : true,ssrPrepass ,config (info ) {const {ctx } =info ;if (typeofwindow !== 'undefined') {// during client requestsreturn {links : [httpBatchLink ({url : '/api/trpc',}),],};}return {links : [httpBatchLink ({// The server needs to know your app's full urlurl : `${getBaseUrl ()}/api/trpc`,/*** Set custom request headers on every request from tRPC* @see https://trpc.io/docs/client/headers*/headers () {if (!ctx ?.req ?.headers ) {return {};}// To use SSR properly, you need to forward client headers to the server// This is so you can pass through things like cookies when we're server-side renderingreturn {cookie :ctx .req .headers .cookie ,};},}),],};},});
utils/trpc.tstsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from './api/trpc/[trpc]';export consttrpc =createTRPCNext <AppRouter >({ssr : true,ssrPrepass ,config (info ) {const {ctx } =info ;if (typeofwindow !== 'undefined') {// during client requestsreturn {links : [httpBatchLink ({url : '/api/trpc',}),],};}return {links : [httpBatchLink ({// The server needs to know your app's full urlurl : `${getBaseUrl ()}/api/trpc`,/*** Set custom request headers on every request from tRPC* @see https://trpc.io/docs/client/headers*/headers () {if (!ctx ?.req ?.headers ) {return {};}// To use SSR properly, you need to forward client headers to the server// This is so you can pass through things like cookies when we're server-side renderingreturn {cookie :ctx .req .headers .cookie ,};},}),],};},});
ou, si vous souhaitez conditionner le SSR à une requête spécifique, vous pouvez passer un callback à ssr. Ce callback peut renvoyer un booléen, ou une Promesse résolvant en booléen :
utils/trpc.tstsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from './api/trpc/[trpc]';export consttrpc =createTRPCNext <AppRouter >({ssrPrepass ,config (info ) {const {ctx } =info ;if (typeofwindow !== 'undefined') {// during client requestsreturn {links : [httpBatchLink ({url : '/api/trpc',}),],};}return {links : [httpBatchLink ({// The server needs to know your app's full urlurl : `${getBaseUrl ()}/api/trpc`,/*** Set custom request headers on every request from tRPC* @see https://trpc.io/docs/client/headers*/headers () {if (!ctx ?.req ?.headers ) {return {};}// To use SSR properly, you need to forward client headers to the server// This is so you can pass through things like cookies when we're server-side renderingreturn {cookie :ctx .req .headers .cookie ,};},}),],};},ssr (opts ) {// only SSR if the request is coming from a botreturnopts .ctx ?.req ?.headers ['user-agent']?.includes ('bot') ?? false;},});
utils/trpc.tstsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from './api/trpc/[trpc]';export consttrpc =createTRPCNext <AppRouter >({ssrPrepass ,config (info ) {const {ctx } =info ;if (typeofwindow !== 'undefined') {// during client requestsreturn {links : [httpBatchLink ({url : '/api/trpc',}),],};}return {links : [httpBatchLink ({// The server needs to know your app's full urlurl : `${getBaseUrl ()}/api/trpc`,/*** Set custom request headers on every request from tRPC* @see https://trpc.io/docs/client/headers*/headers () {if (!ctx ?.req ?.headers ) {return {};}// To use SSR properly, you need to forward client headers to the server// This is so you can pass through things like cookies when we're server-side renderingreturn {cookie :ctx .req .headers .cookie ,};},}),],};},ssr (opts ) {// only SSR if the request is coming from a botreturnopts .ctx ?.req ?.headers ['user-agent']?.includes ('bot') ?? false;},});
pages/_app.tsxtsximport {trpc } from '../utils/trpc';import type {AppProps } from 'next/app';import type {AppType } from 'next/app';constMyApp :AppType = ({Component ,pageProps }:AppProps ) => {return <Component {...pageProps } />;};export defaulttrpc .withTRPC (MyApp );
pages/_app.tsxtsximport {trpc } from '../utils/trpc';import type {AppProps } from 'next/app';import type {AppType } from 'next/app';constMyApp :AppType = ({Component ,pageProps }:AppProps ) => {return <Component {...pageProps } />;};export defaulttrpc .withTRPC (MyApp );
Mise en cache des réponses avec SSR
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.
Vous pouvez utiliser le callback responseMeta sur createTRPCNext pour définir les en-têtes de cache pour les réponses SSR. Consultez également la documentation générale sur la Mise en cache des réponses pour une mise en cache indépendante du framework avec responseMeta.
utils/trpc.tsxtsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from '../server/routers/_app';export consttrpc =createTRPCNext <AppRouter >({config () {if (typeofwindow !== 'undefined') {return {links : [httpBatchLink ({url : '/api/trpc',}),],};}consturl =process .env .VERCEL_URL ? `https://${process .env .VERCEL_URL }/api/trpc`: 'http://localhost:3000/api/trpc';return {links : [httpBatchLink ({url ,}),],};},ssr : true,ssrPrepass ,responseMeta (opts ) {const {clientErrors } =opts ;if (clientErrors .length ) {// propagate http first error from API callsreturn {status :clientErrors [0].data ?.httpStatus ?? 500,};}// cache request for 1 day + revalidate once every secondconstONE_DAY_IN_SECONDS = 60 * 60 * 24;return {headers : newHeaders ([['cache-control',`s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS }`,],]),};},});
utils/trpc.tsxtsximport {httpBatchLink } from '@trpc/client';import {createTRPCNext } from '@trpc/next';import {ssrPrepass } from '@trpc/next/ssrPrepass';import type {AppRouter } from '../server/routers/_app';export consttrpc =createTRPCNext <AppRouter >({config () {if (typeofwindow !== 'undefined') {return {links : [httpBatchLink ({url : '/api/trpc',}),],};}consturl =process .env .VERCEL_URL ? `https://${process .env .VERCEL_URL }/api/trpc`: 'http://localhost:3000/api/trpc';return {links : [httpBatchLink ({url ,}),],};},ssr : true,ssrPrepass ,responseMeta (opts ) {const {clientErrors } =opts ;if (clientErrors .length ) {// propagate http first error from API callsreturn {status :clientErrors [0].data ?.httpStatus ?? 500,};}// cache request for 1 day + revalidate once every secondconstONE_DAY_IN_SECONDS = 60 * 60 * 24;return {headers : newHeaders ([['cache-control',`s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS }`,],]),};},});
Mise en cache des réponses d'API avec l'adaptateur Next.js
Vous pouvez également utiliser responseMeta sur le gestionnaire d'API Next.js pour mettre en cache directement les réponses d'API :
pages/api/trpc/[trpc].tstsximport {initTRPC } from '@trpc/server';import * astrpcNext from '@trpc/server/adapters/next';export constcreateContext = async ({req ,res ,}:trpcNext .CreateNextContextOptions ) => {return {req ,res ,};};typeContext =Awaited <ReturnType <typeofcreateContext >>;export constt =initTRPC .context <Context >().create ();export constappRouter =t .router ({public :t .router ({slowQueryCached :t .procedure .query (async (opts ) => {await newPromise ((resolve ) =>setTimeout (resolve , 5000));return {lastUpdated : newDate ().toJSON (),};}),}),});export typeAppRouter = typeofappRouter ;export defaulttrpcNext .createNextApiHandler ({router :appRouter ,createContext ,responseMeta (opts ) {const {ctx ,paths ,errors ,type } =opts ;// assuming you have all your public routes with the keyword `public` in themconstallPublic =paths &&paths .every ((path ) =>path .includes ('public'));// checking that no procedures erroredconstallOk =errors .length === 0;// checking we're doing a query requestconstisQuery =type === 'query';if (ctx ?.res &&allPublic &&allOk &&isQuery ) {// cache request for 1 day + revalidate once every secondconstONE_DAY_IN_SECONDS = 60 * 60 * 24;return {headers : newHeaders ([['cache-control',`s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS }`,],]),};}return {};},});
pages/api/trpc/[trpc].tstsximport {initTRPC } from '@trpc/server';import * astrpcNext from '@trpc/server/adapters/next';export constcreateContext = async ({req ,res ,}:trpcNext .CreateNextContextOptions ) => {return {req ,res ,};};typeContext =Awaited <ReturnType <typeofcreateContext >>;export constt =initTRPC .context <Context >().create ();export constappRouter =t .router ({public :t .router ({slowQueryCached :t .procedure .query (async (opts ) => {await newPromise ((resolve ) =>setTimeout (resolve , 5000));return {lastUpdated : newDate ().toJSON (),};}),}),});export typeAppRouter = typeofappRouter ;export defaulttrpcNext .createNextApiHandler ({router :appRouter ,createContext ,responseMeta (opts ) {const {ctx ,paths ,errors ,type } =opts ;// assuming you have all your public routes with the keyword `public` in themconstallPublic =paths &&paths .every ((path ) =>path .includes ('public'));// checking that no procedures erroredconstallOk =errors .length === 0;// checking we're doing a query requestconstisQuery =type === 'query';if (ctx ?.res &&allPublic &&allOk &&isQuery ) {// cache request for 1 day + revalidate once every secondconstONE_DAY_IN_SECONDS = 60 * 60 * 24;return {headers : newHeaders ([['cache-control',`s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS }`,],]),};}return {};},});
FAQ
Q : Pourquoi dois-je transférer manuellement les en-têtes du client vers le serveur ? Pourquoi tRPC ne le fait-il pas automatiquement ?
Bien qu'il soit rare de ne pas vouloir transférer les en-têtes du client lors du SSR, vous pourriez vouloir y ajouter des éléments dynamiquement. tRPC ne souhaite donc pas assumer la responsabilité des collisions de clés d'en-têtes, etc.
Q : Pourquoi dois-je supprimer l'en-tête connection lors de l'utilisation du SSR avec Node 18 ?
Si vous ne supprimez pas l'en-tête connection, la récupération des données échouera avec TRPCClientError: fetch failed car connection est un nom d'en-tête interdit.
Q : Pourquoi vois-je encore des requêtes réseau dans l'onglet Réseau ?
Par défaut, @tanstack/react-query (que nous utilisons pour les hooks de récupération de données) réactualise les données au montage et au refocus de la fenêtre, même si des données initiales ont été fournies via SSR. Cela garantit des données toujours à jour. Consultez la page sur le SSG si vous souhaitez désactiver ce comportement.