CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-trpc--next

Next.js integration for tRPC that enables end-to-end type-safe APIs with enhanced server-side rendering and static site generation capabilities.

Pending
Overview
Eval results
Files

ssr.mddocs/

Server-Side Rendering (SSR)

Advanced SSR capabilities with automatic query prefetching, cache dehydration/hydration, and response meta handling for Next.js Pages Router.

Capabilities

ssrPrepass Function

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);

SSR Configuration Options

SSR Enablement

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() { /* ... */ }
};

Response Meta Handling

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",
      },
    };
  },
};

SSR Context and Props

TRPCPrepassProps

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;
}

Custom SSR Prepass Implementation

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),
      },
    };
  };
};

SSR Best Practices

Headers and Cookies

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,
};

Error Boundaries

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,
};

Types

// 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

docs

app-router.md

cache-links.md

index.md

pages-router.md

server-actions.md

ssr.md

tile.json