Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Next.js Integration

Next.js integration provides utilities for using oRPC in Next.js applications, including support for server functions and form actions.

Installation

npm install @orpc/next@beta
pnpm add @orpc/next@beta
yarn add @orpc/next@beta
bun add @orpc/next@beta

Server Functions

Use createServerFunction to turn a procedure into a server function. It accepts the same options as server-side clients, and the returned function accepts the same input as the original procedure.

'use server'

import { const os: Builder<DefaultInitialContext & object, Record<never, never>>
The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler` to define procedures, then compose them into routers.
@see{@link https://orpc.dev/docs/procedure Procedure}
os
} from '@orpc/server'
import { function createServerFunction<TInitialContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TReturnedError extends AnyORPCError>(procedure: Lazyable<Procedure<TInitialContext, any, TInputSchema, TOutputSchema, TErrorMap, TReturnedError>>, ...rest: MaybeOptionalOptions<ProcedureClientOptions<TInitialContext, TOutputSchema, TErrorMap, TReturnedError, object>>): ProcedureServerFunction<TInputSchema, TOutputSchema, TErrorMap, TReturnedError>
Creates a Next.js [server function](https://nextjs.org/docs/app/api-reference/directives/use-server) from a [procedure](https://orpc.dev/docs/procedure). It accepts the same options as server-side clients, and the returned function accepts the same input as the original procedure.
@remarks**Note**: Instead of throwing, the returned function resolves an `[error, data]` tuple with errors serialized as plain JSON (`ORPCErrorJSON`).@see{@link https://orpc.dev/docs/integrations/next#server-functions Next.js Integration - Server Functions}
createServerFunction
} from '@orpc/next'
const const procedure: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>procedure = const os: Builder<DefaultInitialContext & object, Record<never, never>>
The oRPC procedure builder. Chain methods like `.input`, `.use`, and `.handler` to define procedures, then compose them into routers.
@see{@link https://orpc.dev/docs/procedure Procedure}
os
.Builder<DefaultInitialContext & object, Record<never, never>>.handler<string>(handler: ProcedureHandler<DefaultInitialContext & object, unknown, string, ORPCErrorConstructorMap<Record<never, never>>>): DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>handler(async () => 'Hello from oRPC + Next.js!')
export const const serverFunction: ProcedureServerFunction<InitialInputSchema, Schema<string>, Record<never, never>, never>serverFunction =
createServerFunction<{
    user: {
        id: string;
        name: string;
    };
}, InitialInputSchema, Schema<string>, Record<never, never>, never>(procedure: Lazyable<Procedure<{
    user: {
        id: string;
        name: string;
    };
}, any, InitialInputSchema, Schema<string>, Record<never, never>, never>>, options: ProcedureClientOptions<{
    user: {
        id: string;
        name: string;
    };
}, Schema<string>, Record<never, never>, never, object>): ProcedureServerFunction<InitialInputSchema, Schema<...>, Record<...>, never>
Creates a Next.js [server function](https://nextjs.org/docs/app/api-reference/directives/use-server) from a [procedure](https://orpc.dev/docs/procedure). It accepts the same options as server-side clients, and the returned function accepts the same input as the original procedure.
@remarks**Note**: Instead of throwing, the returned function resolves an `[error, data]` tuple with errors serialized as plain JSON (`ORPCErrorJSON`).@see{@link https://orpc.dev/docs/integrations/next#server-functions Next.js Integration - Server Functions}
createServerFunction
(const procedure: DecoratedProcedure<DefaultInitialContext & object, object, InitialInputSchema, Schema<string>, Record<never, never>, never>procedure, {
context: Value<Promisable<{
    user: {
        id: string;
        name: string;
    };
}>, [clientContext: object]>
context
: async () => { // <- provide initial context if needed
return {
user: {
    id: string;
    name: string;
}
user
: { id: stringid: '123', name: stringname: 'Alice' } }
},
interceptors?: ProcedureClientInterceptor<{
    user: {
        id: string;
        name: string;
    };
}, Schema<string>, Record<never, never>, never>[] | undefined
interceptors
: [] // <- add interceptors if needed
})

You can call the returned serverFunction from a client component.

'use client'

import { serverFunction } from './path/to/server/function'

export default function Page() {
  const handleClick = async () => {
    const [error, message] = await serverFunction()

    if (!error) {
      console.log({ message })
    }
  }

  return (
    <div>
      <button onClick={handleClick}>Call Server Function</button>
    </div>
  )
}

Special Next.js errors such as redirect and notFound are rethrown so Next.js handles them normally. All other errors are serialized to ORPCErrorJSON and returned as the first element of the tuple.

Typesafe Errors

Typesafe errors are supported as well. Because errors are serialized before they reach the client, use the inferable field to distinguish errors.

'use client'

import { serverFunction } from './path/to/server/function'

export default function Page() {
  const handleClick = async () => {
    const [error, message] = await serverFunction()

    if (error) {
      if (error.inferable) {
        // handle typesafe error
      }
      else {
        // handle unknown error
      }
    }
    else {
      // handle success case
    }
  }

  return (
    <div>
      <button onClick={handleClick}>Call Server Function</button>
    </div>
  )
}
'use server'

const procedure = os
  .errors({
    NOT_FOUND: {
      message: 'The resource was not found',
    },
  })
  .handler(async ({ errors }) => {
    throw errors.NOT_FOUND()
  })

export const serverFunction = createServerFunction(procedure)

createServerFunctionable

If you reuse the same options across multiple server functions, createServerFunctionable creates a preconfigured helper. The helper takes a procedure and returns a value that works as both a server function and the original procedure on the server.

import { createServerFunctionable } from '@orpc/next'

const functionable = createServerFunctionable({
  context: async () => { // <- provide initial context if needed
    return { user: { id: '123', name: 'Alice' } }
  },
})

// Works as both a server function and a procedure.
export const functionableProcedure = functionable(
  os.handler(async () => 'Hello from oRPC + Next.js!')
)

.actionable Extension

Import @orpc/next/extensions/actionable from a module that always runs during initialization, such as the file where you define your base builder. This adds an .actionable method to decorated procedures. Like createServerFunctionable, it returns a value that works as both a server function and a procedure.

export const functionableProcedure = base
  .handler(async () => 'Hello from oRPC + Next.js!')
  .actionable({
    context: async () => { // <- provide initial context if needed
      return { user: { id: '123', name: 'Alice' } }
    },
  })
import '@orpc/next/extensions/actionable'

import { os } from '@orpc/server'

export const base = os

Hooks

This integration also includes React hooks for server functions. useServerFunction executes a server function and tracks its status. useOptimisticServerFunction does the same, with optimistic updates. Unlike direct server function calls, hook errors are deserialized into native ORPCError instances instead of plain JSON (ORPCErrorJSON) for a more natural developer experience.

'use client'

import { isInferableError } from '@orpc/client'
import {
  getIssueMessage,
  onErrorDeferred,
  parseFormData,
} from '@orpc/next'
import { useServerFunction } from '@orpc/next/hooks'

export function MyComponent() {
  const { execute, data, error, status } = useServerFunction(serverFunction, {
    interceptors: [
      onErrorDeferred((error) => {
        if (isInferableError(error)) {
          console.error(error.data)
          //                   ^ Typed error data
        }
      }),
    ],
  })

  return (
    <form action={form => execute(parseFormData(form))}>
      <input type="text" name="name" required />
      <span>{getIssueMessage(error, 'name')}</span>

      <button type="submit">Submit</button>
      {status === 'pending' && <p>Loading...</p>}
    </form>
  )
}
'use client'

import { useOptimisticServerAction } from '@orpc/next/hooks'
import {
  getIssueMessage,
  onSuccessDeferred,
  parseFormData,
} from '@orpc/next'

export function MyComponent() {
  const [todos, setTodos] = useState<Todo[]>([])
  const { execute, optimisticState } = useOptimisticServerAction(someAction, {
    optimisticPassthrough: todos,
    optimisticReducer: (currentState, newTodo) => [...currentState, newTodo],
    interceptors: [
      onSuccessDeferred(({ data }) => {
        setTodos(prevTodos => [...prevTodos, data])
      }),
    ],
  })

  return (
    <div>
      <ul>
        {optimisticState.map(todo => (
          <li key={todo.todo}>{todo.todo}</li>
        ))}
      </ul>
      <form action={form => execute(parseFormData(form))}>
        <input type="text" name="todo" required />
        <span>{getIssueMessage(error, 'todo')}</span>

        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

Server Form Functions

Use createServerFormFunction to turn a procedure into a form action for <form action={...}>. Unlike createServerFunction, the returned function accepts FormData instead of the procedure input. It deserializes that data using Bracket Notation, then passes the result to the procedure.

export default function Page() {
  return (
    <form action={serverFormFunction}>
      <input name="name" />
      <button type="submit">Submit</button>
    </form>
  )
}
'use server'

import { redirect } from 'next/navigation'

const procedure = os
  .input(z.object({ name: z.string() }))
  .handler(async ({ input }) => {
    // do something
  })

export const serverFormFunction = createServerFormFunction(procedure, {
  interceptors: [
    async ({ next }) => {
      await next()
      redirect('/thank-you') // redirect on success
    }
  ]
})

createServerFormFunctionable

If you reuse the same options across multiple form actions, createServerFormFunctionable creates a preconfigured helper. Like createServerFunctionable, it takes a procedure and returns a value that works as both a server form function and the original procedure.

import { createServerFormFunctionable } from '@orpc/next'

const formFunctionable = createServerFormFunctionable({
  context: async () => { // <- provide initial context if needed
    return { user: { id: '123', name: 'Alice' } }
  },
})

// Works as both a server form function and a procedure.
export const formFunctionableProcedure = formFunctionable(
  os.handler(async () => 'Hello from oRPC + Next.js!')
)

Last updated on August 6, 2026

Was this page helpful?