Boilerplate Tour
stormy init scaffolds the official Next.js boilerplate — both a working starter and a reference implementation of the @stormycms/* packages.
Structure
my-stormy-site/ # pnpm + Turborepo monorepo├── apps/│ ├── site/ # Public CMS-rendered site (localhost:3000)│ └── admin/ # Your admin editor (localhost:4000)└── packages/ ├── components/ # Shared components + CMS export map (index.ts) └── types/ # Shared ambient type declarationsapps/site — the public site
A catch-all route (app/(pages)/[...slug]/page.tsx) resolves every URL to a CMS page via getPageBySlug, maps it with mapPageData, and renders it with LayoutTree + ComponentTree using the shared export map. generateMetadata pulls SEO metadata from the page. Missing slugs redirect to /404. See Examples for the full code.
Needs only NEXT_PUBLIC_SITE_URL, STORMY_CMS_CLIENT_ID, STORMY_CMS_CLIENT_SECRET.
apps/admin — your editor
The customer-owned editing application: page editor, layout editor, and media library. Auth is wired through createNextAuthHandlers at /api/auth/stormy (GitHub/Google OAuth), and the whole app sits inside StormyServerProvider so the session, current site, and site list are available everywhere via useStormy.
Additional env: STORMY_CMS_REDIRECT_URL (OAuth callback), NEXT_PUBLIC_PREVIEW_SITE_URL (points at the public site for previews), and S3_* variables for media uploads to your own bucket.
Admin routes
| Route | Purpose |
|---|---|
/ | Dashboard |
/pages | List and create pages |
/pages/[id] | Page editor (CMSPageProvider + PageEditor) |
/layouts | List and create layouts |
/layouts/[id] | Layout editor (CMSLayoutProvider + LayoutEditor) |
/media | Media library and upload UI |
/api/auth/stormy | OAuth handler from @stormycms/next |
/api/media | Local REST API for listing, uploading, updating, and deleting media (proxies to your S3 bucket and the Stormy CMS media API) |
Media API (/api/media)
The admin exposes a small local REST layer so the browser can upload files without exposing S3 credentials to the client:
GET /api/media— list media items from the CMS (limited to 100).POST /api/media— upload a file to S3 and create aMediarecord in Stormy CMS.PUT /api/media— update a media item's metadata.DELETE /api/media?id=...— delete the media record and the underlying S3 object.
It uses a request-scoped StormyCMSClient with auto-minted JWT via createStormyCMSClient().
packages/components — the shared schema
index.ts is the CMS export map (Components & Schema). The starter inventory: semantic regions (Region, RegionHeader, RegionMain, RegionAside, RegionFooter), Group, Section, Heading, Text, RichText, Image, Navigation, SiteInfo, Card, CardGroup, Hero, CallToAction, and the built-in Outlet.
Add your own components under packages/components/src, register them in index.ts with withCMS, and they appear in the editor immediately. stormy generate can draft registrations from existing component props.
Commands
pnpm dev # run both applicationspnpm quality:light # lint, formatting, and TypeScript checkspnpm test # unit testspnpm test:e2e:install # install Playwright browsers oncepnpm test:e2e # browser testspnpm build # production buildsE2E tests
The admin app has a Playwright suite in apps/admin/e2e/. Tests run against a production build on port 4010 (override with E2E_PORT). For local automation, the test harness can bypass real OAuth by setting E2E_ALLOW_COOKIE_BYPASS=true and sending a cookie named e2e_bypass_auth=true.
Deployment
Deploy apps/site and apps/admin as two separate Next.js applications. Configure each from its .env.example; never commit .env files. Use a restricted S3 credential for media in production. Run pnpm quality before release.
Last updated: 7/9/26, 6:42 AM