Skip to main content
Version: 10.x

静态站点生成

静态站点生成需要在每个页面的 getStaticProps 内执行 tRPC 查询。

¥Static site generation requires executing tRPC queries inside getStaticProps on each page.

这可以使用 服务器端助手 预取查询、脱水并将其传递到页面来完成。然后,查询将自动选取 trpcState 并将其用作初始值。

¥This can be done using server-side helpers to prefetch the queries, dehydrate them, and pass it to the page. The queries will then automatically pick up the trpcState and use it as an initial value.

获取 getStaticProps 中的数据

¥Fetch data in getStaticProps

pages/posts/[id].tsx
tsx
import { createServerSideHelpers } from '@trpc/react-query/server';
import {
GetStaticPaths,
GetStaticPropsContext,
InferGetStaticPropsType,
} from 'next';
import { prisma } from 'server/context';
import { appRouter } from 'server/routers/_app';
import superjson from 'superjson';
import { trpc } from 'utils/trpc';
export async function getStaticProps(
context: GetStaticPropsContext<{ id: string }>,
) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
transformer: superjson, // optional - adds superjson serialization
});
const id = context.params?.id as string;
// prefetch `post.byId`
await helpers.post.byId.prefetch({ id });
return {
props: {
trpcState: helpers.dehydrate(),
id,
},
revalidate: 1,
};
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await prisma.post.findMany({
select: {
id: true,
},
});
return {
paths: posts.map((post) => ({
params: {
id: post.id,
},
})),
// https://next.nodejs.cn/docs/pages/api-reference/functions/get-static-paths#fallback-blocking
fallback: 'blocking',
};
};
export default function PostViewPage(
props: InferGetStaticPropsType<typeof getStaticProps>,
) {
const { id } = props;
const postQuery = trpc.post.byId.useQuery({ id });
if (postQuery.status !== 'success') {
// won't happen since we're using `fallback: "blocking"`
return <>Loading...</>;
}
const { data } = postQuery;
return (
<>
<h1>{data.title}</h1>
<em>Created {data.createdAt.toLocaleDateString('en-us')}</em>
<p>{data.text}</p>
<h2>Raw data:</h2>
<pre>{JSON.stringify(data, null, 4)}</pre>
</>
);
}
pages/posts/[id].tsx
tsx
import { createServerSideHelpers } from '@trpc/react-query/server';
import {
GetStaticPaths,
GetStaticPropsContext,
InferGetStaticPropsType,
} from 'next';
import { prisma } from 'server/context';
import { appRouter } from 'server/routers/_app';
import superjson from 'superjson';
import { trpc } from 'utils/trpc';
export async function getStaticProps(
context: GetStaticPropsContext<{ id: string }>,
) {
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {},
transformer: superjson, // optional - adds superjson serialization
});
const id = context.params?.id as string;
// prefetch `post.byId`
await helpers.post.byId.prefetch({ id });
return {
props: {
trpcState: helpers.dehydrate(),
id,
},
revalidate: 1,
};
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await prisma.post.findMany({
select: {
id: true,
},
});
return {
paths: posts.map((post) => ({
params: {
id: post.id,
},
})),
// https://next.nodejs.cn/docs/pages/api-reference/functions/get-static-paths#fallback-blocking
fallback: 'blocking',
};
};
export default function PostViewPage(
props: InferGetStaticPropsType<typeof getStaticProps>,
) {
const { id } = props;
const postQuery = trpc.post.byId.useQuery({ id });
if (postQuery.status !== 'success') {
// won't happen since we're using `fallback: "blocking"`
return <>Loading...</>;
}
const { data } = postQuery;
return (
<>
<h1>{data.title}</h1>
<em>Created {data.createdAt.toLocaleDateString('en-us')}</em>
<p>{data.text}</p>
<h2>Raw data:</h2>
<pre>{JSON.stringify(data, null, 4)}</pre>
</>
);
}

请注意,react-query 的默认行为是在挂载时在客户端重新获取数据,因此如果你只想通过 getStaticProps 获取数据,则需要在查询选项中将 refetchOnMountrefetchOnWindowFocus 设置为 false

¥Note that the default behaviour of react-query is to refetch the data on the client-side when it mounts, so if you want to only fetch the data via getStaticProps, you need to set refetchOnMount and refetchOnWindowFocus to false in the query options.

如果你想最大程度地减少对 API 的请求数量,这可能会更可取(例如,如果你使用第三方速率限制 API,这可能是必要的)。

¥This might be preferable if you want to minimize the number of requests to your API, which might be necessary if you're using a third-party rate-limited API for example.

这可以针对每个查询来完成:

¥This can be done per query:

tsx
const data = trpc.example.useQuery(
// if your query takes no input, make sure that you don't
// accidentally pass the query options as the first argument
undefined,
{ refetchOnMount: false, refetchOnWindowFocus: false },
);
tsx
const data = trpc.example.useQuery(
// if your query takes no input, make sure that you don't
// accidentally pass the query options as the first argument
undefined,
{ refetchOnMount: false, refetchOnWindowFocus: false },
);

或者在全局内,如果应用中的每个查询都应以相同的方式运行:

¥Or globally, if every query across your app should behave the same way:

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(opts) {
return {
transformer: superjson,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
// Change options globally
queryClientConfig: {
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
},
},
},
},
},
});
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(opts) {
return {
transformer: superjson,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
// Change options globally
queryClientConfig: {
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
},
},
},
},
},
});

如果你的应用混合了静态和动态查询,请谨慎使用此方法。

¥Be careful with this approach if your app has a mixture of static and dynamic queries.