The examples/nextjs directory is a complete working app: FastAPI backend, Next.js App Router frontend, end-to-end type safety. This post walks through every file and what it does.
server.py — the Python backend
from fastapi import FastAPI
from pyrpc_fastapi import mount_fastapi
from pyrpc_core import rpc
app = FastAPI()
@rpc.query
def greet(name: str) -> str:
return f"Hello, {name}!"
@rpc.query
def get_user(user_id: int) -> dict:
return {"id": user_id, "name": "Ada"}
mount_fastapi(app)Two procedures. mount_fastapi(app) registers the POST /rpc endpoint. That is the entire backend.
lib/pyrpc.ts — the client setup
import { createNextClient } from "@pyrpc/next"
import type { Types } from "@pyrpc/types"
import { procedureKinds } from "@pyrpc/types"
export const { api, prefetch, dehydrate, HydrateBoundary } =
createNextClient<Types>({
baseUrl: process.env.NEXT_PUBLIC_PYRPC_URL!,
kinds: procedureKinds,
})One line creates the full client: hooks, prefetch, dehydration, and the hydration boundary. The variable names are yours to choose.
app/layout.tsx — providers
import { api } from "@/lib/pyrpc"
export default function RootLayout({ children }) {
return (
<html>
<body>
<api.Provider>{children}</api.Provider>
</body>
</html>
)
}Wrap once with api.Provider. All child components get access to the hooks.
app/page.tsx — RSC with prefetch
import { prefetch, dehydrate, HydrateBoundary } from "@/lib/pyrpc"
import Greeting from "./greeting"
export default async function Home() {
await prefetch.greet("Ada")
const state = dehydrate()
return (
<HydrateBoundary state={state}>
<Greeting />
</HydrateBoundary>
)
}Server Component prefetches data, dehydrates the cache, passes it to the client via HydrateBoundary. No loading spinners for initial data.
app/greeting.tsx — client hook
"use client"
import { api } from "@/lib/pyrpc"
export default function Greeting() {
const { data } = api.greet.useQuery("Ada")
return <p>{data ?? "Loading..."}</p>
}The client component uses useQuery because greet is a query. If it were a mutation, it would show useMutation automatically.
app/providers.tsx — client provider
"use client"
import { api } from "@/lib/pyrpc"
export function Providers({ children }) {
return <api.Provider>{children}</api.Provider>
}Separate client provider for areas where the root layout is a Server Component but children need client-side hooks.
Why this works
Every file has a single responsibility. The backend is three lines of Python. The frontend is standard Next.js App Router with TanStack Query underneath. pyRPC is the glue, not the framework.

pyRPC