ロゴテキスト ロゴ

    Next.js(App Router利用)でtRPCを動かす

    Next.js(App Router利用)でtRPCを動かす

    フロント/バックエンド側もNext.jsを用いつつtRPCを導入する方法をご紹介します!


    またNext.js 13.4 より安定版が使えるようになったApp Routerを用いて実装していきます。

    検証した環境

    1 node.js 18.15.0
    2 next 13.4.10
    3 @trpc/client 10.36.0
    4 @trpc/next 10.36.0
    5 @trpc/react-query 10.36.0
    6 @trpc/server 10.36.0
    7 @tanstack/react-query 4.32.0

    必要なパッケージを追加

    Next.jsがApp Routerで動く前提で記載していきます


    Next.jsのApp Routerに導入する上で以下のリポジトリが大変参考になりましたmm

    devietti/trpc-next13-app: tRPC + Next 13 app directory + TypeScript



    公式に書かれているパッケージを導入していきます

    $ yarn add @trpc/server @trpc/client @trpc/react-query @trpc/next @tanstack/react-query zod

    バックエンド側を用意

    ここで用意するファイルは2つです

    1. trpc-server.ts・・実際の処理を記載する
    2. route.ts・・Next.js Route Handler機能を用いてtRPCを実行出来るようにする

    trpc-server.ts

    まず、tRPC内で行う実際の処理を記載します



    参考用として

    • アクセスすると「Hello World」というテキストを返す helloWorld
    • ユーザーIDを受け取って該当のユーザーを返す userById

    の2つを定義しています

    src/libs/trpc-server.ts
    import { initTRPC } from '@trpc/server'
    import { z } from 'zod'
     
    const t = initTRPC.create()
     
    interface User {
      id: string
      name: string
    }
    // 参考用のユーザーリスト
    const userList: User[] = [
      { id: '1', name: 'Hoge' },
      { id: '2', name: 'Fuga' },
    ]
     
    export const appRouter = t.router({
      helloWorld: t.procedure.query(() => {
        return 'Hello World'
      }),
      userById: t.procedure
        .input(z.number() /* userById を呼び出す時数値(userId)が必要とする */)
        .query((request) => {
          const { input } = request
          // inputの値を元にユーザーリストから該当するユーザーを返す
          return userList.find((u) => Number.parseInt(u.id) === input)
        }),
    })
     
    // 最終的にフロントでも用いるためexportしておく
    export type AppRouter = typeof appRouter

    余談として、tRPCの↑の処理部分を appRouter という名前で定義するのが定石のようです。

    Next.jsの App Router と名前がバッティングして検索がしづらいという…w 😂


    Next.js Route Handler

    Next.jsの Route Handler機能を使ってtRPCを呼び出せるようにします


    Next.jsのApp RouterになってRoute Handlerの書き方が変わりました。

    Pages Router・App Router どちらを使っているかで以下の記述方法も変わります

    src/app/api/trpc/[trpc]/route.ts
    import { FetchCreateContextFnOptions, fetchRequestHandler } from '@trpc/server/adapters/fetch'
     
    import { appRouter } from '@/libs/trpc-server'
     
    const handler = (request: Request) => {
      console.log(`incoming request ${request.url}`)
      return fetchRequestHandler({
        endpoint: '/api/trpc',
        req: request,
        router: appRouter,
        // eslint-disable-next-line unused-imports/no-unused-vars
        createContext(options: FetchCreateContextFnOptions): object | Promise<object> {
          return {}
        },
      })
    }
     
    export const GET = handler
    export const POST = handler

    ↑へのGET・POSTのアクセス (localhost:3000/api/trpc/${何かしらの値} ) は全て

    先ほど記述したAppRouterに流す、という処理のようですね!


    tRPCにアクセス出来る事を確認

    ローカル環境を起動し以下のURLにアクセスします


    http://localhost:3000/api/trpc/helloWorld

    tRPCに定義したhelloWorldにアクセス出来た


    http://localhost:3000/api/trpc/userById?input=2

    tRPCに定義したuserByIdにアクセス出来た

    どちらも無事アクセス出来る事が確認出来ました! 🎉


    フォルダ構成

    ここまででフォルダ構成は以下のようになっています

    $ tree -L 6
    .
    ├── README.md
    ├── src
    │   ├── app
    │   │   ├── api
    │   │   │   └── trpc
    │   │   │       └── [trpc]
    │   │   │           └── route.ts
    ・・・
    │   ├── libs
    │   │   └── trpc-server.ts
    ・・・

    フロントからtRPCを呼び出す

    いよいよフロントから呼び出します!



    追加を2点

    1. trpc.ts・・フロントでカスタムフックとして使えるようにする
    2. TrpcProvider.tsx・・フロント全体でtRPCを使えるようにする

    変更を1点

    1. layout.tsxでTrpcProvider.tsxをラップする

    です

    trpc.ts

    libsにReactで使える trpc という変数をexportする、trpc.tsを追加します

    src/libs/trpc.ts
    import { createTRPCReact } from '@trpc/react-query'
     
    import type { AppRouter } from './trpc-server'
     
    export const trpc = createTRPCReact<AppRouter>()

    TrpcProvider.tsx

    tRPC用のProviderを追加します


    公式に詳細が記載されていないため把握し切れていませんが、

    tRPCで取得した値の共有・キャッシュの制御なんかをやっているのかな、という予想です

    src/libs/TrpcProvider.tsx
    'use client'
     
    import { FC, PropsWithChildren, useState } from 'react'
     
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
    import { httpBatchLink } from '@trpc/client'
     
    import { trpc } from './trpc'
     
    export const TrpcProvider: FC<PropsWithChildren> = ({ children }) => {
      const [queryClient] = useState(() => new QueryClient())
      const [trpcClient] = useState(() =>
        trpc.createClient({
          links: [httpBatchLink({ url: '/api/trpc' })],
        }),
      )
     
      return (
        <trpc.Provider client={trpcClient} queryClient={queryClient}>
          <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
        </trpc.Provider>
      )
    }

    layout.tsx内でラップする

    ↑で作成したProviderでラップします!


    私は今回アプリケーション全体でtRPCを使用するため、

    1番トップレベルのlayout.tsxでラップしています。


    例えばもっと限定的な場合はその部分のコンポーネントのみでラップすればいいかなと思います。

    src/app/layout.tsx
    import './globals.css'
    import { ReactNode } from 'react'
     
    import { TrpcProvider } from '@/libs/TrpcProvider'
     
    const RootLayout = ({ children }: { children: ReactNode }) => {
      return (
        <TrpcProvider>
          <html lang="ja">
            <body>{children}</body>
          </html>
        </TrpcProvider>
      )
    }
     
    export default RootLayout

    コンポーネントで使用する

    お待たせしました!


    いよいよ、コンポーネントで使用してみます

    src/app/sample/page.tsx
    'use client'
     
    import { NextPage } from 'next'
     
    import { trpc } from '@/libs/trpc'
     
    const SamplePage: NextPage = () => {
      const user = trpc.userById.useQuery(1)
     
      return (
        <main>
          <p>hello, {user.data?.name}</p>
        </main>
      )
    }
     
    export default SamplePage


    ローカルホストを起動し該当ページにアクセスすると

    フロントからもtRPCにアクセス出来た

    無事表示されました!🎉




    分かってくるとここまでの実装もサクッと出来て、

    コード補完もしっかり効くのでDXかなりいいです!☺️



    クライアント側で呼び出しているので "use client" を記述してますが、

    Next.js App RouterのうまみでもあるSSRと連携して使えると更に良さそうですねー!



    フォルダ構成

    最後にここまでの主要ファイルのフォルダ構成です

    $ tree -L 6
    .
    ├── README.md
    ├── src
    │   ├── app
    │   │   ├── api
    │   │   │   └── trpc
    │   │   │       └── [trpc]
    │   │   │           └── route.ts
    │   │   ├── layout.tsx
    │   │   └── sample
    │   │       └── page.tsx
    │   ├── libs
    │   │   ├── TrpcProvider.tsx
    │   │   ├── trpc-server.ts
    │   │   └── trpc.ts


    プロフィールの背景画像 プロフィール画像
    Yuki Takara
    都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。