본문 바로가기
버전: 11.x

서버 사이드 렌더링

비공식 베타 번역

이 페이지는 PageTurner AI로 번역되었습니다(베타). 프로젝트 공식 승인을 받지 않았습니다. 오류를 발견하셨나요? 문제 신고 →

SSR을 활성화하려면 createTRPCNext 설정 콜백에서 ssr: true를 설정하기만 하면 됩니다.

정보

SSR을 활성화하면 tRPC는 서버에서 모든 쿼리를 미리 가져오기 위해 getInitialProps를 사용합니다. 이로 인해 getServerSideProps를 사용할 때 이런 문제가 발생할 수 있으며, 이 해결은 우리의 권한 밖입니다.

 
대안으로 SSR을 비활성화된 상태(기본값)로 두고 서버 사이드 헬퍼를 사용해 getStaticProps 또는 getServerSideProps에서 쿼리를 미리 가져올 수 있습니다.

서버 사이드 렌더링 단계에서 쿼리를 올바르게 실행하려면 config 내부에 추가 로직을 포함해야 합니다:

추가적으로 Response Caching을 고려해보세요.

utils/trpc.ts
tsx
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import superjson from 'superjson';
import type { AppRouter } from './api/trpc/[trpc]';
export const trpc = createTRPCNext<AppRouter>({
ssr: true,
ssrPrepass,
config(config) {
const { ctx } = opts;
if (typeof window !== 'undefined') {
// during client requests
return {
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
};
}
return {
links: [
httpBatchLink({
// The server needs to know your app's full url
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @see https://trpc.io/docs/v10/header
*/
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 rendering
return {
cookie: ctx.req.headers.cookie,
};
},
}),
],
};
},
});
utils/trpc.ts
tsx
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import { ssrPrepass } from '@trpc/next/ssrPrepass';
import superjson from 'superjson';
import type { AppRouter } from './api/trpc/[trpc]';
export const trpc = createTRPCNext<AppRouter>({
ssr: true,
ssrPrepass,
config(config) {
const { ctx } = opts;
if (typeof window !== 'undefined') {
// during client requests
return {
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
};
}
return {
links: [
httpBatchLink({
// The server needs to know your app's full url
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @see https://trpc.io/docs/v10/header
*/
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 rendering
return {
cookie: ctx.req.headers.cookie,
};
},
}),
],
};
},
});

또는 특정 요청에 따라 SSR을 조건부로 적용하려면 ssr에 콜백 함수를 전달할 수 있습니다. 이 콜백은 boolean을 반환하거나 boolean으로 resolve되는 Promise를 반환할 수 있습니다:

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(config) {
const { ctx } = opts;
if (typeof window !== 'undefined') {
// during client requests
return {
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
};
}
return {
links: [
httpBatchLink({
// The server needs to know your app's full url
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @see https://trpc.io/docs/v10/header
*/
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 rendering
return {
cookie: ctx.req.headers.cookie,
};
},
}),
],
};
},
ssr(opts) {
// only SSR if the request is coming from a bot
return opts.ctx?.req?.headers['user-agent']?.includes('bot');
},
});
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(config) {
const { ctx } = opts;
if (typeof window !== 'undefined') {
// during client requests
return {
links: [
httpBatchLink({
url: '/api/trpc',
}),
],
};
}
return {
links: [
httpBatchLink({
// The server needs to know your app's full url
url: `${getBaseUrl()}/api/trpc`,
/**
* Set custom request headers on every request from tRPC
* @see https://trpc.io/docs/v10/header
*/
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 rendering
return {
cookie: ctx.req.headers.cookie,
};
},
}),
],
};
},
ssr(opts) {
// only SSR if the request is coming from a bot
return opts.ctx?.req?.headers['user-agent']?.includes('bot');
},
});
pages/_app.tsx
tsx
import { trpc } from '~/utils/trpc';
import type { AppProps } from 'next/app';
import React from 'react';
const MyApp: AppType = ({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);
pages/_app.tsx
tsx
import { trpc } from '~/utils/trpc';
import type { AppProps } from 'next/app';
import React from 'react';
const MyApp: AppType = ({ Component, pageProps }: AppProps) => {
return <Component {...pageProps} />;
};
export default trpc.withTRPC(MyApp);

FAQ

Q: 클라이언트 헤더를 서버에 수동으로 전달해야 하는 이유는 무엇인가요? tRPC가 자동으로 처리해주지 않는 이유는?

SSR 수행 시 클라이언트 헤더를 서버에 전달하지 않는 경우는 드물지만, 헤더에 동적으로 내용을 추가해야 할 수 있습니다. 따라서 tRPC는 헤더 키 충돌 등에 대한 책임을 지지 않기로 했습니다.

Q: Node 18에서 SSR을 사용할 때 connection 헤더를 삭제해야 하는 이유는?

connection 헤더를 제거하지 않으면 데이터 가져오기가 TRPCClientError: fetch failed로 실패합니다. connection금지된 헤더 이름이기 때문입니다.

Q: 네트워크 탭에서 여전히 네트워크 요청이 보이는 이유는?

기본적으로 데이터 가져오기 훅에 사용하는 @tanstack/react-query는 SSR을 통해 초기 데이터를 이미 받아온 경우에도 마운트 시와 윈도우 포커스 시 데이터를 재요청합니다. 이는 데이터가 항상 최신 상태임을 보장하기 위함입니다. 이 동작을 비활성화하려면 SSG 페이지를 참조하세요.