@snapsite/cms-bake
v0.1.1
Published
CLI + programmatic API + Vite plugin that bakes SnapSite CMS content into a static TS module consumed by client apps.
Downloads
221
Maintainers
Readme
@snapsite/cms-bake
Build-time tool that fetches published CMS content from Supabase and writes it to a TypeScript module consumed by client apps. Ships as a CLI, a programmatic API, and a Vite plugin.
Install
pnpm add -D @snapsite/cms-bakeEnvironment variables
| Name | Where | Purpose |
| --------------------------- | ----- | ------- |
| VITE_CMS_SITE_ID | required | Target site UUID. |
| SUPABASE_URL | required | Project URL (local: http://127.0.0.1:54321). |
| SUPABASE_SERVICE_ROLE_KEY | required, Node only | Bypasses RLS to read all published content. Never ship to client. |
| CMS_OUTPUT_PATH | optional | Where to write. Default src/generated/content.ts. |
CLI
bake-cms-content # normal run
bake-cms-content --output foo/bar.ts # custom path
bake-cms-content --offline # emit an empty stub (useful for initial CI typechecks)
bake-cms-content --helpExits non-zero with a clear message if any required env var is missing.
Programmatic API
import { bake } from "@snapsite/cms-bake";
await bake({
siteId: process.env.VITE_CMS_SITE_ID!,
supabaseUrl: process.env.SUPABASE_URL!,
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
outputPath: "src/generated/content.ts",
skipIfUnchanged: true, // skip write when the output is byte-identical
});Vite plugin
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { cmsBakeVitePlugin } from "@snapsite/cms-bake";
export default defineConfig({
plugins: [
react(),
cmsBakeVitePlugin(),
// Options — each defaults to the matching env var, same as the CLI.
// cmsBakeVitePlugin({ siteId: "...", supabaseUrl: "...", serviceRoleKey: "..." }),
],
});The plugin runs the bake in Vite's buildStart hook — for both vite build and
the dev server. If Supabase credentials are missing, it emits the offline stub
and logs a warning rather than failing the build, so fresh clones without a
seeded local Supabase can still boot.
Output format
src/generated/content.ts is auto-generated. Do not edit by hand.
// AUTO-GENERATED. Do not edit. Run `pnpm bake` to regenerate.
export const BAKED_CONTENT_VERSION = 3 as const;
export const BAKED_CONTENT = {
site: { content_version: 3, id: "...", slug: "..." },
content: {
"/home/hero/headline": "Welcome",
"global.footer": { links: [...] }
},
sections: {
"/home": [ /* Section rows sorted by position */ ]
},
global: {
"footer": { links: [...] } // same rows as content["global.*"], prefix stripped
}
} as const;
export type BakedContent = typeof BAKED_CONTENT;Key ordering is alphabetically stable at every depth — repeated bakes over the same data produce byte-identical files, so accidentally committed output stays git-clean.
What's not baked
- Draft sections (
sections.draft = true) — excluded by default. - Media rows — they carry storage paths, not values. Consumers resolve signed URLs at runtime.
- User-scoped data — bake runs at the site level.
