Next.js Integration
Next.js is a leading React framework for server-rendered apps. oRPC works with both the App Router and Pages Router. For additional context, refer to the HTTP Adapter guide.
INFO
oRPC also provides out-of-the-box support for Server Action with no additional configuration required.
Server
You can integrate oRPC with Next.js using its Route Handlers.
import { RPCHandler } from '@orpc/server/fetch'
const handler = new RPCHandler(router)
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
INFO
The handler
can be any supported oRPC handler, such as RPCHandler, OpenAPIHandler, or another custom handler.
Pages Router Support?
import { RPCHandler } from '@orpc/server/node'
const handler = new RPCHandler(router)
export const config = {
api: {
bodyParser: false,
},
}
export default async (req, res) => {
const { matched } = await handler.handle(req, res, {
prefix: '/rpc',
context: {}, // Provide initial context if needed
})
if (matched) {
return
}
res.statusCode = 404
res.end('Not found')
}
WARNING
Next.js default body parser blocks oRPC raw‑request handling. Ensure bodyParser
is disabled in your API route:
export const config = {
api: {
bodyParser: false,
},
}
Client
Next.js doesn’t natively support isomorphic functions, so you need a workaround to make client-side code compatible with SSR. This example uses globalThis.$headers
as that workaround. Alternatively, you can use React Context like the approach mentioned in discussions#330.
import { RPCLink } from '@orpc/client/fetch'
import type { headers } from 'next/headers'
declare global {
var $headers: typeof headers
}
const link = new RPCLink({
url: new URL('/rpc', typeof window !== 'undefined' ? window.location.href : 'http://localhost:3000'),
headers: async () => {
return globalThis.$headers
? Object.fromEntries(await globalThis.$headers()) // use this on ssr
: {} // use this on browser
},
})
'server only'
import { headers } from 'next/headers'
globalThis.$headers = headers
import '../lib/orpc.server'
// Rest of the code
INFO
This only shows how to configure the link. For full client examples, see Client-Side Clients.
Optimize SSR
To reduce HTTP requests and improve latency during SSR, you can utilize a Server-Side Client during SSR. Below is a quick setup, see Optimize SSR for more details.
import type { RouterClient } from '@orpc/server'
import { RPCLink } from '@orpc/client/fetch'
import { createORPCClient } from '@orpc/client'
declare global {
var $client: RouterClient<typeof router> | undefined
}
const link = new RPCLink({
url: () => {
if (typeof window === 'undefined') {
throw new Error('RPCLink is not allowed on the server side.')
}
return new URL('/rpc', window.location.href)
},
})
/**
* Fallback to client-side client if server-side client is not available.
*/
export const client: RouterClient<typeof router> = globalThis.$client ?? createORPCClient(link)
'server only'
import { headers } from 'next/headers'
import { createRouterClient } from '@orpc/server'
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(),
}),
})
import '../lib/orpc.server'
// Rest of the code