Storybook is published as its own dedicated Netlify site, separate from the main Astro
app, on the same GitHub repo (shawn-sandy/513). Every push to primary rebuilds and
republishes the src/components/react/** stories at a dedicated URL via Git CI — there is
no manual deploy step and no deploy:storybook script.
How publishing works — the shared-dist/ model
Section titled “How publishing works — the shared-dist/ model”Netlify reads the same root netlify.toml for every site linked to the repo, and a
publish value there overrides each site’s dashboard setting. Rather than fight that, both
sites publish the same dist/ directory — each one’s own build command fills dist/
with its own artifact.
App site
Build command npm run build → Astro SSR + static output in dist/.
Storybook site
Build command npm run build-storybook:netlify (storybook build -o dist) → static component
docs in dist/.
The two builds run in separate, isolated Netlify build environments, so they never collide.
publish = "dist" stays pinned in netlify.toml exactly as it always has — so the app
deploy is untouched, and no per-site dashboard publish pin is required.
The publish flow
Section titled “The publish flow”-
Author or edit a story under
src/components/react/**(see below). -
Preview locally with the Storybook dev server:
Terminal window npm run storybook # http://localhost:6006 -
Verify the production build before pushing:
Terminal window bash scripts/verify-storybook-build.sh -
Commit and push to
primary. The Storybook Netlify site auto-buildsnpm run build-storybook:netlifyand publishesdist/. -
Confirm it is live at the Storybook site URL — the manager loads with your stories in the sidebar.
Authoring a story so it publishes
Section titled “Authoring a story so it publishes”Stories are discovered by this glob (from .storybook/main.ts):
Directorysrc/components/react/
DirectoryMyComponent/
- MyComponent.tsx
- MyComponent.stories.tsx ← discovered and published
Use the project’s CSF3 + @storybook/react-vite conventions:
import type { Meta, StoryObj } from '@storybook/react'import { MyComponent } from '#components/react/MyComponent/MyComponent'
const meta = { title: 'React/MyComponent', component: MyComponent,} satisfies Meta<typeof MyComponent>
export default metatype Story = StoryObj<typeof meta>
export const Default: Story = { args: { label: 'Hello' },}Secret safety — the bundle is public
Section titled “Secret safety — the bundle is public”The Storybook site is unauthenticated. Anything a story imports at build time is baked into the static bundle and served publicly. Two rules:
No server-only imports
Do not import server-only modules in a story (Astro.locals, #libs/database, anything pulling
getViteConfig). They will crash a headless build or leak into the bundle.
No secret values
Never import a module that embeds a real credential at module scope. Publishable Clerk keys
(pk_*) are fine; secret keys, JWTs, Supabase/Turso credentials are not.
scripts/verify-storybook-build.sh enforces this: it builds Storybook and hard-fails if a
secret value (Clerk sk_, Supabase sbp_, a JWT, a PEM private key, whsec_, chpt_,
xaat-) is baked into the bundle. Run it before every publish — and it is the right thing to
wire into CI as a status check.
Netlify site configuration (one-time)
Section titled “Netlify site configuration (one-time)”The dedicated Storybook site is created once in the Netlify dashboard. The app site needs no changes.
| Setting | Storybook site |
|---|---|
| Build command | npm run build-storybook:netlify |
| Publish directory | dist (shared, from netlify.toml) |
| Framework | None / Other (disable Astro autodetect) |
| Production branch | primary |
| Node version | 22.12.0 (from root netlify.toml) |
Verifying a live deploy
Section titled “Verifying a live deploy”A guarded Playwright spec checks the deployed site renders real stories (skipped unless
STORYBOOK_URL is set, so it never fails the app repo’s npm run test:e2e):
STORYBOOK_URL=https://<site>.netlify.app npx playwright test e2e/storybook-deploy.spec.tsTroubleshooting
Section titled “Troubleshooting”The site is publishing the Astro dist/. Its build command is wrong — set it to npm run build-storybook:netlify, not npm run build or plain npm run build-storybook.
Confirm the file matches src/components/react/**/*.stories.@(js|jsx|mjs|ts|tsx) and has a
valid default export with a title.
A story pulled in Astro.locals, #libs/database, or a getViteConfig path. Stub or remove
that import — the Storybook Vite config is deliberately Astro-free.
A credential value is in the bundle. The script prints the offending file path (never the value). Remove the build-time import that pulls the secret in.
Reference
Section titled “Reference”- Operational detail:
.storybook/README.md→ “Deploying Storybook” - Authoring & visual testing: Storybook & Chromatic
- Verify script:
scripts/verify-storybook-build.sh - Live-deploy E2E:
e2e/storybook-deploy.spec.ts