Share Graft CMS across Astro and Next, remove Sanity, pin Bun 1.4

Merged
AN
2mo ago
+1727-4533

Summary

Graft is now the CMS for both site versions. Sanity is gone.

  • Shared CMS: schema + MDX live in packages/content (@andy-portfolio/content). graft compile writes the static SQLite index both apps read.
  • Astro (apps/web, port 3000): still the live Vercel site. Blog bodies stay on Astro content collections + @astrojs/mdx.
  • Next (apps/web-next, port 3001): restored as a workspace so bun dev / bun lint / bun build include it. Not deployed. Blog uses @usegraft/sdk-next MdxBody; projects and case studies come from the same Graft collections.
  • Removed: apps/studio, packages/sanity-config, Sanity export script, revalidate webhooks, Sanity env on Turbo/CI.
  • Bun 1.4.0 is pinned (packageManager, CI setup-bun). First 1.4 install wrote configVersion: 1 in bun.lock (still hoisted).

Two versions + Graft

VersionPathPortDeploy
Astro 7apps/web3000Vercel (Root Directory apps/web)
Next.js 16.3apps/web-next3001Local archive only

Author posts, projects, and case studies once under packages/content/content/.

Vercel stays Astro-only: apps/web/vercel.json buildCommand is bun run build (the Astro app script, which compiles Graft then runs astro build). There is no apps/web-next/vercel.json.

Index path (dev + prerender)

The compiled SQLite file lives at packages/content/.graft/index.db. Resolving it via import.meta.url broke in two bundlers:

  • Astro prerender rewrote it to dist/server/.prerender/.graft/index.db
  • Next Turbopack resolved @andy-portfolio/content to a virtual [project]/packages/content path that is not on disk

getIndexPath() now confirms the candidate exists (or falls back to cwd / GRAFT_CONTENT_ROOT) at open time.

CI Next build (no secrets)

Lint’s new web-next build step has no Spotify/Resend secrets. Next was throwing while collecting /api/spotify/now-playing because env.ts parsed those keys at import. The archive now matches Astro: lazy getSpotifyEnv / getResendEnv, Resend constructed inside handlers, Spotify idle {isPlaying:false} when env is missing. Verified locally with those variables unset.

Greptile P1: stale static sitemap

Deleted apps/web-next/public/sitemap.xml so src/app/sitemap.ts owns /sitemap.xml (Graft posts + /case-studies/*, no obsolete /case-study/*). Deleted the same file from apps/web/public/ so Vercel does not ship it beside @astrojs/sitemap (sitemap-index.xml / sitemap-0.xml). Confirmed Next :3001/sitemap.xml includes i-built-smol and both case studies.

Bun 1.3.14 vs 1.4.0

Same machine (Linux), warm node_modules + bun cache, 3 runs, median. GNU time for wall + max RSS; bun --version also timed with perf_counter (GNU time rounds sub-10ms to 0.00). Nested scripts used the version under test via PATH.

1.3.14 can still bun install --frozen-lockfile against this 1.4 configVersion: 1 lockfile (no rewrite).

Command1.3.14 median1.4.0 medianNotes
bun --version startup1.00 ms (5.7 MB)0.85 ms (4.0 MB)20 warm runs after dropping the first
bun install --frozen-lockfile0.02 s (25.2 MB)0.02 s (18.6 MB)Warm graph; RSS is the real 1.4 win here
content compile0.43 s0.44 sGraft CLI; wash
bun run lint1.28 s1.27 sTurbo --force; wash
Astro web build5.89 s (934 MB)5.96 s (1178 MB)Work is Node 22 (Astro/Vite)
Next web-next build3.96 s4.02 sIncremental .next after the first 1.4 run

Do not read this as live-site TTFB. Vercel’s Astro compile still runs on Node 22. Bun 1.4 only changes local/CI bun install and bun run of those scripts.

Verification

Lint, format, Astro typecheck, Next tsc, Astro production build, and Next production build all passed locally after the index-path fix.

agent-browser walk of Astro :3000 and Next :3001: /, /projects (SEO filter), both blog posts, both case studies, Astro mobile viewport. Same Graft titles, no cdn.sanity.io.

Astro home Selected Works
Next home Selected Works
Astro projects SEO filter
Astro Smol blog post
Next Smol blog post
Astro projects on mobile

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Summary by cubic

Replaces Sanity with a shared Graft CMS for both the live Astro site and the local Next.js archive, so both apps read the same compiled content index from @andy-portfolio/content, and pins Bun to 1.4.0.

Changes

  • Moves schema and MDX into @andy-portfolio/content; graft compile writes the SQLite index both apps read.
  • Astro keeps its content collections for blog bodies; the Next app uses @usegraft/sdk-next for MDX and reads the same index for projects and case studies.
  • Resolves the index path at open time via @andy-portfolio/content/package.json, with a cwd fallback for Turbopack's virtual paths, so Astro's prerendered build finds it.
  • Allows 127.0.0.1 as a dev origin in the Next app so agent-browser can reach the dev server.
  • Reads Next Spotify and Resend env lazily so next build works in CI without secrets.
  • Removes apps/studio, packages/sanity-config, the Sanity export script, and the revalidate webhook routes.
  • Adds apps/web-next back to the Turbo workspace and updates its dev/build scripts to compile Graft first.
  • Removes both static public/sitemap.xml files so the generated sitemaps own /sitemap.xml.
  • Pins Bun to 1.4.0 in packageManager and the CI setup.

Migration

  • Run bun install to update the lockfile.
  • Remove Sanity environment variables and webhook secrets from any deployments or local env files.