Skip to main content
Version: 10.x

Server-Side Rendering

To enable SSR just set ssr: true in your createTRPCNext config callback.

caution

When you enable SSR, tRPC will use getInitialProps to prefetch all queries on the server. This results in problems like this when you use getServerSideProps and solving it is out of our hands.

 
Alternatively, you can leave ssr disabled(the default) and use SSG Helpers to prefetch queries in getStaticProps or getServerSideProps.

In order to execute queries properly during the server-side render step we need to add extra logic inside our config:

Additionally, consider Response Caching.

utils/trpc.ts
tsx
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';
import type { AppRouter } from './api/trpc/[trpc]';
export const trpc = createTRPCNext<AppRouter>({
config({ ctx }) {
if (typeof window !== 'undefined') {
// during client requests
return {
transformer: superjson, // optional - adds superjson serialization
links: [
httpBatchLink({
url: '/api/trpc',
}),
]
};
}
// The server needs to know your app's full url
const url = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}/api/trpc`
: 'http://localhost:3000/api/trpc';
return {
transformer: superjson, // optional - adds superjson serialization
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @link https://trpc.io/docs/v10/header
*/
headers() {
if (ctx?.req) {
// To use SSR properly, you need to forward the client's headers to the server
// This is so you can pass through things like cookies when we're server-side rendering
// If you're using Node 18, omit the "connection" header
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
connection: _connection,
...headers
} = ctx.req.headers;
return {
...headers,
// Optional: inform server that it's an SSR request
'x-ssr': '1',
};
}
return {};
},
}),
]
};
},
ssr: true,
});
utils/trpc.ts
tsx
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';
import type { AppRouter } from './api/trpc/[trpc]';
export const trpc = createTRPCNext<AppRouter>({
config({ ctx }) {
if (typeof window !== 'undefined') {
// during client requests
return {
transformer: superjson, // optional - adds superjson serialization
links: [
httpBatchLink({
url: '/api/trpc',
}),
]
};
}
// The server needs to know your app's full url
const url = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}/api/trpc`
: 'http://localhost:3000/api/trpc';
return {
transformer: superjson, // optional - adds superjson serialization
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @link https://trpc.io/docs/v10/header
*/
headers() {
if (ctx?.req) {
// To use SSR properly, you need to forward the client's headers to the server
// This is so you can pass through things like cookies when we're server-side rendering
// If you're using Node 18, omit the "connection" header
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
connection: _connection,
...headers
} = ctx.req.headers;
return {
...headers,
// Optional: inform server that it's an SSR request
'x-ssr': '1',
};
}
return {};
},
}),
]
};
},
ssr: true,
});
pages/_app.tsx
tsx
import type { AppProps } from 'next/app';
import React from 'react';
import { trpc } from '~/utils/trpc';
const MyApp: AppType = ({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);
pages/_app.tsx
tsx
import type { AppProps } from 'next/app';
import React from 'react';
import { trpc } from '~/utils/trpc';
const MyApp: AppType = ({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);

FAQ

Q: Why do I need to forward the client's headers to the server manually? Why doesn't tRPC automatically do that for me?

While it's rare that you wouldn't want to forward the client's headers to the server when doing SSR, you might want to add things dynamically in the headers. Therefore, tRPC doesn't want to take responsibility for header keys colliding, etc.

Q: Why do I need to delete the connection header when using SSR on Node 18?

If you don't remove the connection header, the data fetching will fail with TRPCClientError: fetch failed because connection is a forbidden header name.