Next.js integration for tRPC that enables end-to-end type-safe APIs with enhanced server-side rendering and static site generation capabilities.
—
Advanced SSR capabilities with automatic query prefetching, cache dehydration/hydration, and response meta handling for Next.js Pages Router.
Built-in SSR prepass implementation that automatically prefetches queries during server-side rendering.
/**
* Built-in SSR prepass implementation for automatic query prefetching
* Handles server-side rendering with query cache dehydration and hydration
* @param opts - Configuration object with parent options, components, and prepass helper
*/
export const ssrPrepass: TRPCPrepassHelper;
type TRPCPrepassHelper = (opts: {
parent: WithTRPCSSROptions<AnyRouter>;
WithTRPC: NextComponentType<any, any, any>;
AppOrPage: NextComponentType<any, any, any>;
}) => void;Usage Examples:
import { withTRPC } from "@trpc/next";
import { httpBatchLink } from "@trpc/client";
import { ssrPrepass } from "@trpc/next/ssrPrepass";
import type { AppRouter } from "../pages/api/[trpc]";
const MyApp = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
export default withTRPC<AppRouter>({
config({ ctx }) {
return {
links: [
httpBatchLink({
url: "/api/trpc",
headers: ctx?.req?.headers,
}),
],
};
},
ssr: true,
ssrPrepass, // Use the built-in prepass implementation
})(MyApp);Configure when SSR should be enabled for your application.
interface WithTRPCSSROptions<TRouter extends AnyRouter> {
/** Enable SSR with optional conditional function */
ssr: true | ((opts: { ctx: NextPageContext }) => boolean | Promise<boolean>);
}Usage Examples:
// Always enable SSR
const alwaysSSR = {
ssr: true,
ssrPrepass,
config() { /* ... */ }
};
// Conditional SSR based on route
const conditionalSSR = {
ssr: ({ ctx }) => {
// Only SSR for dashboard pages
return ctx.pathname?.startsWith("/dashboard") ?? false;
},
ssrPrepass,
config() { /* ... */ }
};
// Async conditional SSR
const asyncConditionalSSR = {
ssr: async ({ ctx }) => {
// Check user agent or other async conditions
const userAgent = ctx.req?.headers["user-agent"];
return !userAgent?.includes("bot");
},
ssrPrepass,
config() { /* ... */ }
};Configure HTTP response status codes and headers based on tRPC errors and application state.
interface WithTRPCSSROptions<TRouter extends AnyRouter> {
/** Function to set response meta like headers and status codes */
responseMeta?: (opts: {
ctx: NextPageContext;
clientErrors: TRPCClientError<TRouter>[];
}) => ResponseMeta;
}
interface ResponseMeta {
status?: number;
headers?: Record<string, string>;
}Usage Examples:
const withResponseMeta = {
ssr: true,
ssrPrepass,
config() { /* ... */ },
responseMeta({ ctx, clientErrors }) {
const firstError = clientErrors[0];
if (firstError) {
// Set appropriate HTTP status based on tRPC error
switch (firstError.data?.code) {
case "UNAUTHORIZED":
return { status: 401 };
case "FORBIDDEN":
return { status: 403 };
case "NOT_FOUND":
return { status: 404 };
case "INTERNAL_SERVER_ERROR":
return { status: 500 };
default:
return { status: 400 };
}
}
// Set security headers
return {
headers: {
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
},
};
},
};Props passed to components during the SSR prepass phase.
interface TRPCPrepassProps<TRouter extends AnyRouter, TSSRContext extends NextPageContext = NextPageContext> {
/** tRPC client configuration used during prepass */
config: WithTRPCConfig<TRouter>;
/** React Query client instance */
queryClient: QueryClient;
/** tRPC client instance for making requests */
trpcClient: TRPCUntypedClient<TRouter> | TRPCClient<TRouter>;
/** Current SSR state indicator */
ssrState: 'prepass';
/** Next.js page context during SSR */
ssrContext: TSSRContext;
}For advanced use cases, you can implement custom SSR prepass logic.
// Custom prepass example
const customPrepass: TRPCPrepassHelper = ({ parent, WithTRPC, AppOrPage }) => {
WithTRPC.getInitialProps = async (appOrPageCtx) => {
const ctx = appOrPageCtx.ctx;
// Your custom SSR logic here
const config = parent.config({ ctx });
const queryClient = getQueryClient(config);
const trpcClient = createTRPCUntypedClient(config);
// Manually prefetch specific queries
await queryClient.prefetchQuery({
queryKey: ["user", "profile"],
queryFn: () => trpcClient.user.getProfile.query(),
});
// Dehydrate and return state
const dehydratedState = dehydrate(queryClient);
return {
pageProps: {
trpcState: transformer.input.serialize(dehydratedState),
},
};
};
};Pass request headers and cookies to maintain session state during SSR.
import { httpBatchLink } from "@trpc/client";
const ssrConfig = {
config({ ctx }) {
return {
links: [
httpBatchLink({
url: "/api/trpc",
headers: {
// Forward all request headers
...ctx?.req?.headers,
// Or specific headers
cookie: ctx?.req?.headers?.cookie,
authorization: ctx?.req?.headers?.authorization,
},
}),
],
};
},
ssr: true,
ssrPrepass,
};Handle SSR errors gracefully to prevent complete page failures.
const robustSSRConfig = {
config() { /* ... */ },
ssr: async ({ ctx }) => {
try {
// Check if SSR is appropriate
return ctx.pathname !== "/client-only-page";
} catch (error) {
// Fall back to client-side rendering on error
console.error("SSR check failed:", error);
return false;
}
},
responseMeta({ clientErrors }) {
// Don't fail the entire page for client errors
if (clientErrors.length > 0) {
console.error("Client errors during SSR:", clientErrors);
}
return {};
},
ssrPrepass,
};// Next.js page context type
interface NextPageContext {
req?: IncomingMessage;
res?: ServerResponse;
pathname: string;
query: ParsedUrlQuery;
asPath?: string;
locale?: string;
locales?: string[];
defaultLocale?: string;
}
// tRPC configuration with SSR support
interface WithTRPCConfig<TRouter extends AnyRouter> extends CreateTRPCClientOptions<TRouter>, CreateTRPCReactQueryClientConfig {
abortOnUnmount?: boolean;
}
// tRPC client error type
interface TRPCClientError<TRouter extends AnyRouter> extends Error {
data?: {
code: string;
httpStatus?: number;
};
shape?: any;
}Install with Tessl CLI
npx tessl i tessl/npm-trpc--next