Published on

Next.js 13 + TRPC Guide - Best Practices

Authors

Introduction

Next.js 13, the latest iteration of the popular React framework, combined with tRPC, an efficient data fetching and API client for React, opens up exciting possibilities for web developers. In this comprehensive guide, we will explore the best practices for using Next.js 13 and tRPC to create high-performance, data-driven web applications.

Dive into the powerful synergy between Next.js 13 and tRPC in this comprehensive guide.

Discover how this dynamic duo empowers web developers to create blazing-fast and highly interactive applications. From leveraging Next.js 13’s cutting-edge features to harnessing tRPC's facilities that provide a type-safe way to build APIs.

Without wasting any more of your time, lets get straight into it!

Setup Next.js 13 Project Environment

To setup a Next.js project, please check out the instructions here. The installation process generally consists of running the following command in the terminal.


  npx create-next-app@latest

Dependencies Installation

Open a terminal within your project directory, and execute one of the following commands to get started:


npm install @trpc/client @trpc/server @trpc/react-query @tanstack/react-query zod
yarn add @trpc/client @trpc/server @trpc/react-query @tanstack/react-query zod
pnpm add @trpc/client @trpc/server @trpc/react-query @tanstack/react-query zod

Creating a tRPC Server

  1. Go into the src/ folder in your Next.js 13 project — or inside the root of your project if you didn’t select the src/ folder option — and create a new folder called server with a file called trpc.ts:

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

const t = initTRPC.create();

export const router = t.router;
export const publicProcedure = t.procedure;

This is simple boilerplate code to allow you to create API procedures and routers in your Next.js 13 backend.

  1. Inside the src/server/ directory, create a new file called index.ts:

import { z } from "zod"

import { publicProcedure, router } from "./trpc"

export const appRouter = router({
  getData: publicProcedure.query(async () => {
    // Here you would fetch data from a database in a
    // real-life application.
    console.log("getData")
    return "getData"
  }),
  setData: publicProcedure
    .input(z.string())
    .mutation(async ({ input }) => {
      // Here you would update a database using the
      // input string passed into the procedure
      console.log(input)
      return input
    }),
})
// This type will be used as a reference later...
export type AppRouter = typeof appRouter

  • We import the publicProcedure and router functions defined inside the trpc.ts file.
  • Next we create an appRouter, which acts as a hub for your API procedures.
  • Inside the appRouter I defined a trivial getData and setData function for tutorial’s sake.
  • In the setData function, the code input(z.string()) ensures the input data is validated using zod maximizing endpoint security and developer experience.
  1. Go inside the app/ directory, and create a sequence of files and directories as follows: api/trpc/[trpc]/route.ts.

import { appRouter } from "@/server"
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: "/api/trpc",
    req,
    router: appRouter,
    createContext: () => ({}),
  })

export { handler as GET, handler as POST }

This creates an API endpoint that is called when a procedure is run.

Configuring tRPC for Client-Side and Server-Side

  1. Inside the app/ directory, create a folder called _trpc and with a file called client.ts. This defines a tRPC instance that is usable inside all your Next.js 13 client-components.

import { type AppRouter } from "@/server"
import { createTRPCReact } from "@trpc/react-query"

export const trpc = createTRPCReact<AppRouter>({})

  1. Within the same folder, create a file called serverClient.ts to define a tRPC instance that works for all server-side components.

import { appRouter } from "@/server"
import { httpBatchLink } from "@trpc/client"

export const serverClient = appRouter.createCaller({
  links: [
    httpBatchLink({
      url: "http://localhost:3000/api/trpc",
    }),
  ],
})

NOTE: You will need to change the url http:localhost:3000 when deploying your code to production, In this case, you should conditionally select the url as follows:


import { appRouter } from "@/server"
import { httpBatchLink } from "@trpc/client"

const url =
  process.env.NODE_ENV === "production"
    ? "your-production-url/api/trpc"
    : "http://localhost:3000/api/trpc"

export const serverClient = appRouter.createCaller({
  links: [
    httpBatchLink({ url }),
  ],
})

Server tRPC Example

Lets run our procedures that we’ve defined earlier inside a server component! Open up app/page.tsx and copy the following code:


import { serverClient } from "./_trpc/serverClient"

export default async function page() {
  const data = await serverClient.getData()
  const dataSet = await serverClient.setData("test-data")

  return (
    <main>
        <div>{data}</div>
        <div>{dataSet}</div>
    </main>
  )
}

Run the application using npm run dev inside a terminal window, then visit localhost:3000/; you should see the data within the UI and within the server-side terminal window.

Client tRPC Example

Now lets run the same procedures inside a client component! Open up app/page.tsx and copy the following code:


"use client"

import { trpc } from "@/app/_trpc/client"

export default async function page() {
  const getData = trpc.getData.useQuery({
      // your react-query properties ...
  })

  const setData = trpc.setData.useMutation({
      // your react-query properties ...
  })

  return (
    <main>
        <div>{getData.data}</div>
        <div>{setData.data}</div>
    </main>
  )
}

Conclusion:

In both the server-side and client-side examples, you may have noticed that the data returned is 100% type-safe! This is the main reason tRPC is so great!

Your Next.js 13 project workflow should now increase tenfold as the data from external endpoints is 100% validated + type-safe, ready to bring your projects to life.

I hope you’ve enjoyed this article! You now have a fully functional and scalable tRPC backend within your Next.js 13 project!

If you have any questions and concerns, please be sure to let me know, and as always, stay tuned for more! 👍.

FAQs

  1. What is Next.js 13, and how does it differ from previous versions?

Next.js 13 introduces various improvements, including enhanced performance and a smaller bundle size, making it an excellent choice for modern web development.

  1. Why should I consider using tRPC with Next.js 13?

tRPC simplifies data fetching and API integration in React applications, streamlining development and improving code quality.

  1. How can I optimize SEO in a Next.js 13 + tRPC project?

Optimizing SEO involves various techniques, such as using descriptive meta tags, optimizing images, and improving overall page load speed.

  1. What are some common deployment platforms for Next.js 13 + tRPC applications?

You can deploy your project to various platforms, including Vercel, Netlify, and AWS Amplify, depending on your preferences and requirements.

  1. Are there any performance considerations when using Server-Side Rendering in Next.js 13?

While SSR can significantly improve performance and SEO, it's essential to consider server resources and caching strategies to ensure optimal results.