Build a typed full-stack slice: Python procedures with @rpc.query / @rpc.mutation, a Next.js App Router UI, RSC prefetch, and client hooks — all through @pyrpc/next.
Prefer a ready-made tree? Clone the repo and open examples/nextjs. This tutorial walks the same path from scratch.
What you will build
- FastAPI + pyRPC API on port 8000
- Next.js app that prefetches on the server and hydrates on the client
- A query hook and a mutation hook wired to TanStack Query
Step 1 — Python API
# server.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pyrpc_core import rpc
from pyrpc_fastapi import mount_fastapi
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
@rpc.query
def greet(name: str = "World") -> str:
return f"Hello, {name}!"
@rpc.query
async def get_status() -> dict:
return {"status": "online", "version": "0.8.1"}
@rpc.mutation
def set_display_name(name: str) -> dict:
return {"ok": True, "name": name}
mount_fastapi(app)
# uv run uvicorn server:app --reload --port 8000Bare @rpc still works and defaults to kind query. Prefer explicit @rpc.query / @rpc.mutation for new code.
Step 2 — Generate types
# with the API running pyrpc codegen # or: npx pyrpc sync in server distribution mode
You should get Types, ProcedureKinds, and a runtime procedureKinds object in @pyrpc/types.
Step 3 — Install the Next stack
npm install @pyrpc/next @pyrpc/react @pyrpc/client @pyrpc/types @tanstack/react-query npm install next react react-dom
Step 4 — createNextClient
// lib/pyrpc.ts
import { createNextClient } from "@pyrpc/next"
import type { Types } from "@pyrpc/types"
import { procedureKinds } from "@pyrpc/types"
export const {
api,
createCaller,
prefetch,
dehydrate,
HydrateClient,
} = createNextClient<Types, typeof procedureKinds>({
baseUrl: process.env.PYRPC_URL ?? "http://localhost:8000",
kinds: procedureKinds,
})Naming matches createReactClient. The Next factory returns a bundle because App Router needs server helpers, not only hooks.
Step 5 — Provider
// app/providers.tsx
"use client"
import { NextPyRPCProvider } from "@pyrpc/next"
export function Providers({ children }: { children: React.ReactNode }) {
return <NextPyRPCProvider>{children}</NextPyRPCProvider>
}
// app/layout.tsx — wrap children with <Providers>This is TanStack Query’s cache provider. pyRPC does not add a separate RPC context.
Step 6 — Prefetch in a Server Component
// app/page.tsx
import { dehydrate, HydrateClient, prefetch } from "@/lib/pyrpc"
import { Greeting } from "./greeting"
export default async function Page() {
await prefetch.greet("Ada")
await prefetch.get_status(undefined)
return (
<HydrateClient state={dehydrate()}>
<Greeting />
</HydrateClient>
)
}Step 7 — Client Component hooks
// app/greeting.tsx
"use client"
import { api } from "@/lib/pyrpc"
export function Greeting() {
const { data, isLoading } = api.greet.useQuery("Ada")
const status = api.get_status.useQuery(undefined)
const rename = api.set_display_name.useMutation()
return (
<div>
<p>{isLoading ? "…" : data}</p>
<p>{status.data?.status}</p>
<button onClick={() => rename.mutate("Grace")}>Rename</button>
</div>
)
}With procedureKinds, set_display_name only exposes useMutation — calling useQuery on it is a type error.
Step 8 — Run both processes
# terminal 1 uv run uvicorn server:app --reload --port 8000 # terminal 2 npm run dev # Next on :3000
Common pitfalls
- Missing baseUrl in RSC — Server Components have no
window. Always setbaseUrl/PYRPC_URL. - Forgetting the provider — hooks throw without a QueryClient. Use
NextPyRPCProvideror rawQueryClientProvider. - CORS — allow
http://localhost:3000on the FastAPI app during local dev. - Stale kinds — after changing
@rpc.query/@rpc.mutation, regenerate types.

pyRPC