Rendering på serversidan
Denna sida har översatts av PageTurner AI (beta). Inte officiellt godkänd av projektet. Hittade du ett fel? Rapportera problem →
För att aktivera SSR behöver du bara sätta ssr: true i din createTRPCNext-konfigurationscallback.
När du aktiverar SSR kommer tRPC att använda getInitialProps för att förhämtra alla queries på servern. Detta leder till problem som detta när du använder getServerSideProps, och att lösa detta ligger utanför vår kontroll.
Alternativt kan du lämna SSR inaktiverat (standardinställningen) och använda Server-Side Helpers för att förhämta queries i getStaticProps eller getServerSideProps.
För att kunna köra queries korrekt under renderingen på serversidan behöver vi lägga till extra logik i vår config. Överväg också att använda 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 ,};},}),],};},});
eller, om du vill aktivera SSR villkorligt baserat på en specifik förfrågan, kan du skicka en callback till ssr. Denna callback kan returnera ett booleskt värde eller ett Promise som resolver till ett booleskt värde:
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 );
Cachelagring av svar med SSR
Om du aktiverar SSR i din app kan du upptäcka att den laddas långsamt på t.ex. Vercel, men du kan faktiskt rendera hela appen statiskt utan SSG; läs denna Twitter-tråd för mer insikt.
Du kan använda responseMeta-callbacken på createTRPCNext för att ställa in cache-headers för SSR-svar. Se även den allmänna dokumentationen om Cachelagring av svar för ramverksoberoende cachelagring med 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 }`,],]),};},});
Cachelagring av API-svar med Next.js-adaptern
Du kan också använda responseMeta i Next.js API-handlern för att cachelagra API-svar direkt:
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 {};},});
Vanliga frågor
F: Varför måste jag vidarebefordra klientens headers till servern manuellt? Varför gör inte tRPC detta automatiskt?
Även om det är ovanligt att man inte vill vidarebefordra klientens headers till servern vid SSR, kan du vilja lägga till saker dynamiskt i headern. Därför vill tRPC inte ta ansvar för potentiella kollisioner av header-nycklar etc.
F: Varför måste jag ta bort connection-headern när jag använder SSR på Node 18?
Om du inte tar bort connection-headern kommer datahämtningen att misslyckas med TRPCClientError: fetch failed eftersom connection är ett förbjudet headernamn.
F: Varför ser jag fortfarande nätverksförfrågorna i Network-fliken?
Som standard kommer @tanstack/react-query (som vi använder för datahämtningshooks) att hämta data igen vid mount och fönsterfokusering, även om den redan har initialdata via SSR. Detta säkerställer att data alltid är aktuell. Se sidan om SSG om du vill inaktivera detta beteende.