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 deployagainstDATABASE_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-productcreates the repository, mints the five brokered credentials (Cloudflare and Supabase) and lands the whole product as one pull request. Its convergence onmainprovisions and deploys it per environment in a fixed order:supabase ──▶ cloudflare-hyperdrive ──▶ db-migrate ──▶ webEach edge is
mode: enforcedon a push to main and advisory on a pull request. -
02-docsrecords 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 baseline | Where |
|---|---|
| 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 renderer | hooks/render-deployment-docs.sh |
| The product's context pack | ai/context/ |
| The baseline's tests: contract, leak, offline bootstrap and plans, end to end | testing/, .github/workflows/baseline.yml |
Cutting a baseline-vN tag | .github/workflows/tag.yml |
What is here (the product)
| Surface | Where |
|---|---|
| The product's name (the page title) | apps/web/lib/site.ts |
| The blog: the feed, drafts, a post, the new-draft form, the header | apps/web/pages, apps/web/components |
| The write routes and the author check | apps/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 page | apps/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-tests | apps/web/pages/api/health.ts |
| Workers build, bindings and deploy contract | apps/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 runner | packages/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 step | infra/terraform/supabase, infra/terraform/cloudflare-hyperdrive, infra/db-migrate |
| Tests | tests/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
| Vercel | Here |
|---|---|
| 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 process | The 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.js | runtime = "workerd": the query compiler is a compiled WebAssembly module, left by webpack for wrangler to bundle |
prisma migrate deploy against the production database | infra/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 owner | Generated once per database into app_private.secrets; a SECRET Orun secret, pushed as a Worker secret, overrides it |
GITHUB_ID, GITHUB_SECRET | Optional 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_URL | AUTH_TRUST_HOST: next-auth reads the origin from each request, so OAuth calls back to the host the visitor is on |
getStaticProps with revalidate: 10 | getServerSideProps: every page reads the database per request, and hands the session to the page |
| A database created in Prisma's or Vercel's dashboard | infra/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
| Workflow | What it runs | Ships to a product |
|---|---|---|
ci.yml | the 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 them | yes |
baseline.yml | the 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 roles | no |
tag.yml | cuts a baseline-vN tag (manual) | no |
Build from the console
Press Build: Fullstack Prisma Blog goes into the workspace you choose (or a new one), you connect Cloudflare and Supabase, and an agent builds it.
Build from the CLI
# check providers, then build
orun baseline check prisma-nextjs-blog
orun baseline new prisma-nextjs-blog --via-platform --repo acme/prisma-nextjs-blog