Django + Next.js is a production-grade combination: Django's admin, ORM, and auth on the backend; Next.js App Router with RSC prefetch on the frontend. pyRPC handles the contract between them, no API layer to maintain, no schema drift.
Server
# myproject/views.py
from pyrpc_core import rpc
@rpc.query
async def greet(name: str = "World") -> dict:
return {"message": f"Hello, {name}!", "framework": "Django"}
@rpc.query
async def read_item(item_id: int, q: str = None) -> dict:
return {"item_id": item_id, "q": q}
@rpc.mutation
async def create_item(name: str, description: str = None) -> dict:
return {"name": name, "description": description, "created": True}
# myproject/urls.py
from . import views # required to register @rpc decorators
from pyrpc_django import mount_django
urlpatterns = [
path("", views.index),
]
mount_django(urlpatterns)
# myproject/settings.py
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]Start pyrpc dev
cd server uv add pyrpc-core[django] pyrpc dev --yes --module myproject.views --client ../client
Client
// lib/pyrpc.ts
import { createNextClient } from "@pyrpc/next"
import type { Types } from "@pyrpc/types"
export const api = createNextClient<Types>({
baseUrl: process.env.PYRPC_URL ?? "http://localhost:8000",
})// app/page.tsx, Server Component
import { api } from "@/lib/pyrpc"
import { Counter } from "./counter"
export default async function Page() {
await api.prefetch.greet({ name: "Django User" })
await api.prefetch.read_item({ item_id: 42, q: "django-test" })
return (
<api.HydrationBoundary state={api.dehydrate()}>
<Counter />
</api.HydrationBoundary>
)
}
// app/counter.tsx, Client Component
"use client"
export function Counter() {
const { data: greeting, isLoading } = api.greet.useQuery({ name: "Django User" })
const { data: item } = api.read_item.useQuery({ item_id: 42, q: "django-test" })
const createItem = api.create_item.useMutation()
const [name, setName] = useState("")
return (
<div>
{isLoading ? <p>Loading…</p> : <pre>{JSON.stringify(greeting)}</pre>}
<pre>{JSON.stringify(item)}</pre>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => createItem.mutate({ name })} disabled={createItem.isPending}>
{createItem.isPending ? "Creating…" : "Create"}
</button>
{createItem.isSuccess && <pre>{JSON.stringify(createItem.data)}</pre>}
</div>
)
}Run it
cd server && pyrpc dev cd client && npm install && npm run dev
Open http://localhost:3000. Full source at examples/django-nextjs.
Why Django + Next.js?
Django gives you a production-grade admin panel, migrations, the ORM, and auth out of the box. Next.js gives you App Router, RSC, and first-class TypeScript. pyRPC's thin adapter layer means you never write a REST endpoint, Django procedures call through to your models directly, and the types land in Next.js automatically.

pyRPC