Fullstack Blog with Next.js + PrismaCloudflareSupabase

Fullstack Prisma Blog

A Next.js blog on Prisma with GitHub sign-in, its Postgres on Supabase.

prisma-nextjs-blog

Vercel's Fullstack Blog with Next.js + Prisma template (prisma/fullstack-prisma-nextjs-blog) — a public feed of published posts, drafts, a post page in Markdown, and GitHub sign-in through next-auth with its Prisma adapter, all read and written with Prisma — running on Cloudflare Workers + Static Assets through @opennextjs/cloudflare, with the database a Supabase Postgres project reached through Cloudflare Hyperdrive, both provisioned by Terraform through Orun and the schema applied by Orun's db-migrate, and packaged as an Orun baseline: one command takes a new product from nothing to live on stage and prod, in its own repository, under its own name — in ONE landing.

The template's code is the code. What the port changes is what the Workers runtime and a fresh, unattended deployment force:

  • The Prisma client is generated for the Workers runtime and built per request over the Hyperdrive binding (lib/prisma.ts).
  • The schema is a migration applied before the app serves, where upstream ran prisma migrate deploy against DATABASE_URL.
  • The secret next-auth signs its cookies with is generated by the database itself.
  • GitHub sign-in is connected by the owner after the build; the blog serves its feed signed-out until then.
  • The pages read per request instead of at build time; server code reads the session with getServerSession.
  • Publish and delete check that the post is the visitor's own, where upstream let any signed-in visitor publish or delete any post.

Next.js moves from 13.5 to 16.3.8, and react-markdown from 8 to 10, for React 19. Every table in public has row-level security, so Supabase's Data API serves none of it — not a post, not a session token, not an OAuth token. What the baseline adds is everything needed to make a copy of it somebody else's — built the way postgres-drizzle-starter is. See specs/epics/prisma-nextjs-blog for the design, the milestones and the as-built status, including every departure.

Instantiating a product

git clone --depth 1 --branch <baseline-tag> https://github.com/sourceplane/prisma-nextjs-blog
cd prisma-nextjs-blog
orun new --blueprint repo-blueprint.yaml --out ~/products/acme-blog --run-hooks --resume \
  --set reponame=acme-blog --set productname="Acme Journal" \
  --set githuborg=acme-inc --set subdomain=<workers-dev-subdomain>

Or, once the platform's registry lists it, from the Orun console (Baselines → prisma-nextjs-blog → Build). There are two phases:

  • 01-product creates the repository, mints the five brokered credentials (Cloudflare and Supabase) and lands the whole product as one pull request. Its convergence on main provisions and deploys it per environment in a fixed order:

    supabase ──▶ cloudflare-hyperdrive ──▶ db-migrate ──▶ web
    

    Each edge is mode: enforced on a push to main and advisory on a pull request.

  • 02-docs records the live deployment from probed facts.

BOOTSTRAP.md is the operator guide; docs/phases/ has a page per phase. The Supabase organization needs room for two projects (stage and prod) — the free plan's limit.

The new repository receives the product only — the app, the Terraform roots, the migrations, the tests, its CI, its tooling, a README of its own and its operating contract — renamed throughout to the product's name, repository, Worker, Supabase project and Hyperdrive config. None of this repository's machinery ships. The card asks for no third-party key: GitHub sign-in's OAuth App is the owner's to add afterwards (the product's README, "Connecting GitHub sign-in").

What makes it a baselineWhere
The console card (inputs, secrets, programme)blueprint.yaml
The build document (modules, phases, hooks)repo-blueprint.yaml
This tree's identity, the product's README and the bootstrap's task contracts.baseline/
The rename engine (product-name seam: apps/web/lib/site.ts)tooling/rebrand/rebrand.mjs
The bootstrap's helpers (deferrals, redeploy marker, work-tree strip)tooling/bootstrap/
The deployment-record rendererhooks/render-deployment-docs.sh
The product's context packai/context/
The baseline's tests: contract, leak, offline bootstrap and plans, end to endtesting/, .github/workflows/baseline.yml
Cutting a baseline-vN tag.github/workflows/tag.yml

What is here (the product)

SurfaceWhere
The product's name (the page title)apps/web/lib/site.ts
The blog: the feed, drafts, a post, the new-draft form, the headerapps/web/pages, apps/web/components
The write routes and the author checkapps/web/pages/api/post, apps/web/pages/api/publish, apps/web/lib/own-post.ts
next-auth: its options, the cookie secret, GitHub sign-in, the not-connected pageapps/web/lib/auth.ts, apps/web/lib/auth-secret.ts, apps/web/lib/github.ts, apps/web/pages/api/auth, apps/web/pages/auth/not-connected.tsx
The Prisma schema and the per-request client (Hyperdrive first, DATABASE_URL fallback)apps/web/prisma/schema.prisma, apps/web/lib/prisma.ts
The database check the deploy smoke-testsapps/web/pages/api/health.ts
Workers build, bindings and deploy contractapps/web/next.config.mjs, open-next.config.ts, wrangler.template.jsonc → wrangler.jsonc (committed), component.yaml, tooling/wire/worker-secrets.mjs
The schema, row-level security and the cookie secret as migrations, and Lumen's runnerpackages/db (@saas/db — the name the db-migrate composition builds); apps/web/prisma/migrations is upstream's migration for a local database
The Supabase project, the Hyperdrive config, the migration stepinfra/terraform/supabase, infra/terraform/cloudflare-hyperdrive, infra/db-migrate
Teststests/web, tests/db (vitest; tests/db runs the migrations in PGlite)

Getting started

pnpm install
pnpm run wire:fixture   # re-render apps/web/wrangler.jsonc from its template (committed; this changes nothing)
pnpm typecheck
pnpm lint
pnpm test
pnpm build              # prisma generate + next build + opennextjs-cloudflare build → apps/web/.open-next

The build needs no database. Running it locally — a Postgres on localhost:54322 (supabase start), pnpm --filter @app/web db:migrate, then next dev or wrangler dev — is in the product's README (.baseline/product/README.md), which is what a product built from here carries.

How it maps to Vercel

VercelHere
Next.js 13.5 hosting (Pages Router)One Cloudflare Worker (.open-next/worker.js) + Static Assets, on Next.js 16.3.8, still the Pages Router; built with webpack
Prisma Postgres or any Postgres (DATABASE_URL), one client per processThe Supabase project's Postgres through Hyperdrive: getPrisma() builds a PrismaClient per request over @prisma/adapter-pg from env.HYPERDRIVE.connectionString; DATABASE_URL is for local work and the Prisma CLI
The Prisma client generated for Node.jsruntime = "workerd": the query compiler is a compiled WebAssembly module, left by webpack for wrangler to bundle
prisma migrate deploy against the production databaseinfra/db-migrate applies packages/db/src/migrations per environment — upstream's schema, row-level security, the cookie secret — after the project and the Hyperdrive config exist, before the Worker deploys
SECRET, set by the ownerGenerated once per database into app_private.secrets; a SECRET Orun secret, pushed as a Worker secret, overrides it
GITHUB_ID, GITHUB_SECRETOptional Orun secrets per environment, pushed to the Worker as Worker secrets at deploy; unset, no sign-in and a "Log in" page that says how to connect GitHub
NEXTAUTH_URLAUTH_TRUST_HOST: next-auth reads the origin from each request, so OAuth calls back to the host the visitor is on
getStaticProps with revalidate: 10getServerSideProps: every page reads the database per request, and hands the session to the page
A database created in Prisma's or Vercel's dashboardinfra/terraform/supabase (one project per environment) + infra/terraform/cloudflare-hyperdrive (one config per environment), ids and credentials as orun secrets
"Deploy with Vercel" (clone the template)orun new --blueprint repo-blueprint.yaml — a new repository, branded, provisioned, migrated, deployed

CI

WorkflowWhat it runsShips to a product
ci.ymlthe Orun pipeline, and the only workflow a product carries: orun plan --changed, then one job per lane. A pull request runs the verify lanes (the app built and dry-deployed per environment, both Terraform roots checked and planned, the migrations planned), except the bootstrap's own landings (orun/BASE-*), where plan alone runs and nothing waits for it — the landing was checked in the build box; a merge applies, migrates and deploys stage → prod. The lanes do not typecheck, lint or run the tests: baseline.yml's e2e tier does, on the landing, before each tag. It plans nothing while intent.yaml names the placeholder workspace ws_CHANGE_ME, as this repository does. A manual run (Orunbase's Deploy button, or Run workflow on main) plans the environment and components it names under the push-to-main trigger, so they deploy as a merge deploys themyes
baseline.ymlthe baseline's own tiers: contract tests; the blueprint read by the pinned orun, the leak gate, the offline bootstrap and the product's own plans; the product built, migrated into a local Postgres, dry-deployed and served — the feed, "Log in" with GitHub not connected, an author's draft written, published and deleted while another writer is refused — and its tables and secret shut to the Data API's rolesno
tag.ymlcuts a baseline-vN tag (manual)no