Skip to content
You are reading the v2 docs, currently in beta.V1 docs
oRPC
Esc
navigateopen⌘Jpreview
On this page

Next.js Adapter

Use oRPC inside a Next.js project by mounting a handler in a route handler.

Next.js is a leading React framework for server-rendered apps. oRPC works with both the App Router and Pages Router through the Fetch API Adapter and Node HTTP Adapter respectively.

Server

You set up an oRPC server inside Next.js using its Route Handlers.

import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

async function handleRequest(request: Request) {
  const { response } = await handler.handle(request, {
    prefix: '/rpc',
    context: {} // Provide initial context if needed
  })

  return response ?? new Response('Not found', { status: 404 })
}

export const HEAD = handleRequest
export const GET = handleRequest
export const POST = handleRequest
export const PUT = handleRequest
export const PATCH = handleRequest
export const DELETE = handleRequest
Pages Router Support?
import type { NextApiRequest, NextApiResponse } from 'next'
import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/node'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

export const config = {
  api: {
    bodyParser: false,
  },
}

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { matched } = await handler.handle(req, res, {
    prefix: '/api/rpc',
    context: {} // Provide initial context if needed
  })

  if (matched) {
    return
  }

  res.statusCode = 404
  res.end('Not found')
}

Client

By leveraging headers from next/headers, you can configure the link to work seamlessly in both browser and server environments:

import { RPCLink } from '@orpc/client/fetch'

const link = new RPCLink({
  url: '/rpc',
  origin: typeof window === 'undefined' ? 'http://localhost:3000' : undefined, // defaults to the current origin in the browser
  headers: async () => {
    if (typeof window !== 'undefined') {
      return {}
    }

    const { headers } = await import('next/headers')
    return await headers()
  },
})

Optimize SSR

To reduce HTTP requests and improve latency during SSR, you can use a server-side client during SSR. Below is a quick setup, see Optimizing SSR for more details.

import type { RouterClient } from '@orpc/server'
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'

if (import.meta.env.SSR) {
  await import('./orpc.server')
}

declare global {
  var $client: RouterClient<typeof router> | undefined
}

const link = new RPCLink({
  url: '/rpc',
  origin: () => {
    if (typeof window === 'undefined') {
      throw new Error('This link is not allowed on the server side.')
    }

    return window.location.origin
  },
})

/**
 * Fall back to a browser client when no SSR client is registered.
 */
export const client: RouterClient<typeof router> = globalThis.$client ?? createORPCClient(link)
import { createRouterClient } from '@orpc/server'
import { headers } from 'next/headers'

globalThis.$client = createRouterClient(router, {
  /**
   * Provide initial context if needed.
   *
   * Because this client instance is shared across all requests,
   * only include context that's safe to reuse globally.
   * For per-request context, use middleware context or pass a function as the initial context.
   */
  context: async () => ({
    headers: await headers(), // provide headers if initial context required
  }),
})

Last updated on August 25, 2026

Was this page helpful?