Are you interested in using tRPC in the new Next.js 13 app directory? Look no further! In this article, I’ll walk you through the process of setting up a tRPC server and client in the Next.js 13 app directory step by step.

It’s worth noting that tRPC is not currently officially supported in React Server Components, which Next.js 13 has adopted. However, you can still use tRPC in Client Side Components by including the "use client" flag at the top of the files.

One challenge that you may encounter is how to hydrate a fetch call made in a client-side component that’s fetched through SSR and mounted on the client. Unfortunately, React Query doesn’t yet support this feature, but the tRPC team is working on a workaround to enable tRPC to be used in both React Server and Client components.

You can check out the experimental playground for tRPC + Next.js 13 at https://github.com/trpc/next-13, and also review discussions and possible workarounds for these issues at https://github.com/trpc/trpc/discussions/3185, https://github.com/trpc/trpc/issues/3297, and https://twitter.com/alexdotjs/status/1598240673211510784.

That being said, in this article, we’ll focus on setting up tRPC in Next.js 13 where only the client will make the RPC calls. So, let’s get started!

More practice:

Setup tRPC Server and Client in Next.js 13 App Directory

Setup the Next.js 13 Project

When you finish this tutorial, your app will resemble the screenshot below. The app will fetch a list of users from the tRPC backend through an RPC call made by the tRPC client. Once the request is successful, the app will display the fetched users on the page.

Final App of the Next.js 13 tRPC Setup Project

First, choose a directory on your computer to store the project source code. Then, depending on your preferred package manager, run one of the commands below to initiate the setup of your Next.js 13 project:


yarn create next-app nextjs13-trpc-setup
# or
npx create-next-app@latest nextjs13-trpc-setup
# or
pnpm create next-app nextjs13-trpc-setup

As you proceed through the setup process, you’ll encounter several prompts that require your attention. Firstly, you’ll be prompted to enable TypeScript and ESLint, and you should select “Yes” for both. Then, choose “Yes” for both the experimental app/ directory and src/ directory options. Lastly, you’ll be prompted to select an import alias, where you should press the Tab key to choose the first option and then press Enter.

After responding to these prompts, the project will be created, and all necessary dependencies will be automatically installed. Once the installation process is complete, you can open the project in your favourite IDE or text editor.

Create a Client-Side tRPC Provider

tRPC relies on React Query to communicate with the tRPC server. However, using React Query in a Server Component is not supported, and doing so will result in an error.

To address this limitation, we can create a custom Client Component to render both the QueryClient and tRPC providers on the client-side. By doing so, we can ensure that all subsequent client components have access to both the tRPC client and the query client.

In Next.js 13, creating a component that lives only on the client-side is as simple as adding the "use client" flag at the top of the file. This instructs the server to skip rendering the component on the server-side.

To create the custom component, start by creating a ‘utils‘ folder inside the ‘src‘ directory. Within the ‘utils‘ folder, create a trpc-provider.tsx file and add the following code.

src/utils/trpc-provider.tsx


"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink, getFetch, loggerLink } from "@trpc/client";
import { useState } from "react";
import superjson from "superjson";
import { trpc } from "./trpc";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: { queries: { staleTime: 5000 } },
      })
  );

  const url = process.env.NEXT_PUBLIC_VERCEL_URL
    ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
    : "http://localhost:3000/api/trpc/";

  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        loggerLink({
          enabled: () => true,
        }),
        httpBatchLink({
          url,
          fetch: async (input, init?) => {
            const fetch = getFetch();
            return fetch(input, {
              ...init,
              credentials: "include",
            });
          },
        }),
      ],
      transformer: superjson,
    })
  );
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        {children}
        <ReactQueryDevtools />
      </QueryClientProvider>
    </trpc.Provider>
  );
};

Add the tRPC Provider to Next.js 13

Now that we have created a custom client-side provider that wraps around the React Query and tRPC providers, we need to ensure it’s rendered at the root of the app so that all client components can access it. This can be achieved by wrapping the provider around the {children} node in the src/app/layout.tsx file, which is the main entry point of the application.

As the Layout component is a Server Component, it will be able to directly render the provider since the provider component has been marked as a Client component.

To accomplish this, open the src/app/layout.tsx file and replace its contents with the following code.

src/app/layout.tsx


import { TrpcProvider } from "@/utils/trpc-provider";

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <TrpcProvider>{children}</TrpcProvider>
      </body>
    </html>
  );
}

Create the tRPC App Router

At this point, we’re ready to initialize the tRPC backend, create a tRPC router, and define an RPC that retrieves a list of users when invoked by the tRPC client. Originally, I had planned to include Prisma and PostgreSQL to store the user list, but I decided against it as it could make the project more complicated and introduce more variables. The focus of this tutorial is to showcase how tRPC can be used in the new Next.js 13 app directory.

To begin, navigate to the src/app/api directory and create a trpc folder. Inside the trpc folder, create a trpc-router.ts file and paste the following code.

src/app/api/trpc/trpc-router.ts


import { initTRPC } from "@trpc/server";
import superjson from "superjson";

const t = initTRPC.create({
  transformer: superjson,
});

export const appRouter = t.router({
  getUsers: t.procedure.query(({ ctx }) => {
    return userList;
  }),
});

export type AppRouter = typeof appRouter;

// The code below is kept here to keep things simple

interface User {
  id: string;
  name: string;
  email: string;
}

const userList: User[] = [
  {
    id: "1",
    name: "John Doe",
    email: "johndoe@gmail.com",
  },
  {
    id: "2",
    name: "Abraham Smith",
    email: "abrahamsmith@gmail.com",
  },
  {
    id: "3",
    name: "Barbie Tracy",
    email: "barbietracy@gmail.com",
  },
  {
    id: "4",
    name: "John Payday",
    email: "johnpayday@gmail.com",
  },
  {
    id: "5",
    name: "Remember My Name",
    email: "remembermyname@gmail.com",
  },
  {
    id: "6",
    name: "Go to School",
    email: "gotoschool@gmail.com",
  },
  {
    id: "7",
    name: "Fish Fruit",
    email: "fishfruit@gmail.com",
  },
  {
    id: "8",
    name: "Don't try",
    email: "donttry@gmail.com",
  },
  {
    id: "9",
    name: "Producer Feed",
    email: "producerfeed@gmail.com",
  },
  {
    id: "10",
    name: "Panic So",
    email: "panicso@gmail.com",
  },
];

Create the tRPC HTTP Handler

When creating the tRPC API, it’s important to note that the Next.js adaptor is not currently compatible with the Next.js 13 app directory. This means that the createNextApiHandler function cannot be used to create the API handler in the app router. To work around this limitation, we’ll use the fetch adaptor, which is built on top of the standard web Request and Response objects.

To create the tRPC API, navigate to the src/app/api/trpc directory and create a new dynamic directory named [trpc]. Within this directory, create a route.ts file and add the following code.

src/app/api/trpc/[trpc]/route.ts


import {
  FetchCreateContextFnOptions,
  fetchRequestHandler,
} from "@trpc/server/adapters/fetch";
import { appRouter } from "../trpc-router";

const handler = (request: Request) => {
  console.log(`incoming request ${request.url}`);
  return fetchRequestHandler({
    endpoint: "/api/trpc",
    req: request,
    router: appRouter,
    createContext: function (
      opts: FetchCreateContextFnOptions
    ): object | Promise<object> {
      return {};
    },
  });
};

export { handler as GET, handler as POST };

Create the tRPC Hooks

Now that the tRPC API has been created, it’s time to generate the tRPC hooks for the procedures defined on the server. However, since we’re using Next.js 13 and the @trpc/next package is not yet compatible with it, we can’t use the ‘createTRPCNext’ function to generate the hooks. Instead, we’ll use the createTRPCReact function provided by the @trpc/react-query package.

To generate the hooks, create a trpc.ts file in the src/utils folder and add the following code to it:

src/utils/trpc.ts


import type { AppRouter } from "@/app/api/trpc/trpc-router";
import { createTRPCReact } from "@trpc/react-query";

export const trpc = createTRPCReact<AppRouter>();

Make an API Request

With the new tRPC hook in place, you can now call the API from your React components. Since we only have one server procedure in this demo, we’ll use the hook to fetch the list of users. Keep in mind that we can’t make this RPC call directly from a Server Component, so we’ll create a client-side component and use the hook to make the call.

To get started, create a new file called ListUsers.tsx in the app directory and add the following code:

src/app/ListUsers.tsx


"use client";

import { trpc } from "@/utils/trpc";
import React from "react";

export default function ListUsers() {
  let { data: users, isLoading, isFetching } = trpc.getUsers.useQuery();

  if (isLoading || isFetching) {
    return <p>Loading...</p>;
  }

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr 1fr 1fr",
        gap: 20,
      }}
    >
      {users?.map((user) => (
        <div
          key={user.id}
          style={{ border: "1px solid #ccc", textAlign: "center" }}
        >
          <img
            src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
            alt={user.name}
            style={{ height: 180, width: 180 }}
          />
          <h3>{user.name}</h3>
        </div>
      ))}
    </div>
  );
}

To render the ListUsers.tsx component we created earlier, we need to include it in the src/app/page.tsx file. To do this, open the src/app/page.tsx file and replace its contents with the following code.

src/app/page.tsx


import ListUsers from "./ListUsers";

export default function Home() {
  return (
    <main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
      <ListUsers />
    </main>
  );
}

Congratulations! You have successfully integrated tRPC into your Next.js 13 app directory project. To see the application in action, start the Next.js development server and navigate to the root route. Keep in mind that the RPC call is made on the client-side, so you may experience a brief loading state before the list of users appears on the page.

Conclusion

The source code for the tRPC + Next.js 13 app directory project can be found on GitHub. I hope you found this tutorial helpful and enjoyable. If you have any feedback or questions, please feel free to leave a comment below.