← Back to Blog

Flask + Next.js: App Router with a Flask backend

·9 min read

Flask + Next.js is an interesting combination: a lightweight Python server paired with a full-featured SSR frontend. Because pyRPC's transport is just POST /rpc, the @pyrpc/next adapter doesn't care whether the server is FastAPI or Flask, the setup is identical.

Server

# server/main.py
from flask import Flask
from flask_cors import CORS
from pyrpc_core import rpc
from pyrpc_flask import mount_flask

app = Flask(__name__)
CORS(app, origins=["http://localhost:3000"])

@rpc.query
def greet(name: str = "World") -> dict:
    return {"message": f"Hello, {name}!", "framework": "Flask"}

@rpc.query
def read_item(item_id: int, q: str = None) -> dict:
    return {"item_id": item_id, "q": q}

@rpc.mutation
def create_item(name: str, description: str = None) -> dict:
    return {"name": name, "description": description, "created": True}

mount_flask(app)

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=5000)

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:5000",  // Flask port
})
// app/page.tsx (Server Component)
import { api } from "@/lib/pyrpc"
import { Counter } from "./counter"

export default async function Page() {
  await api.prefetch.greet({ name: "Flask User" })
  await api.prefetch.read_item({ item_id: 42, q: "flask-test" })
  return (
    <api.HydrationBoundary state={api.dehydrate()}>
      <Counter />
    </api.HydrationBoundary>
  )
}
// app/counter.tsx ("use client")
import { api } from "@/lib/pyrpc"
import { useState } from "react"

export function Counter() {
  const [name, setName] = useState("")
  const { data: greeting, isLoading } = api.greet.useQuery({ name: "Flask User" })
  const { data: item } = api.read_item.useQuery({ item_id: 42, q: "flask-test" })
  const createItem = api.create_item.useMutation()

  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 && uv add pyrpc-core[flask] && pyrpc dev --yes
cd client && npm install && npm run dev

Open http://localhost:3000. Full source at examples/flask-nextjs.