Summary: Storyblok is the headless CMS that powers all marketing pages, magazin, podcast pages, redirects, and other non-product content rendered by nuxt-website. Both 7Mind and 7Sleep share a single Storyblok space, accessed via one API token. Sources: direct code inspection (nuxt.config.ts, pages/_dynamicPage/, pages/storyblok.vue, middleware/redirects.js, store/storyblok.js, terraform/web-v2/SECRETS.md) Last updated: 2026-05-15


Role in the system

Storyblok is the source of truth for editorial content on the public web. Most pages that are not part of the auth/checkout/account flow are rendered by fetching a Storyblok story and mapping its body blocks to Vue components.

The Nuxt module @storyblok/nuxt-2 is configured in nuxt-website via:

  • Module registration in buildModules of nuxt.config.ts
  • Access token from env var NUXT_ENV_STORYBLOK_TOKEN
  • cacheProvider: 'memory'

There is one Storyblok space backing both brands. The access token does not encode the brand. Brand-specific filtering happens in client/server code.

What Storyblok owns

  • Landing pages (pages/_dynamicPage/landing/)
  • Legal pages (pages/_dynamicPage/legal/)
  • Catch-all dynamic routes under _dynamicPage/_subroute/_subsubroute
  • Magazin articles and previews
  • Podcast pages
  • Campaign pages (campaigns/*)
  • Multiplier and B2B pages (multiplier-pages/*, b2b-pages/*)
  • Redirect definitions (Storyblok stories under a redirects/ folder feed into the redirect middleware)
  • Global components (under global/)

How content gets rendered

  1. At build time, fetchStoryblokPages() in nuxt.config.ts calls cdn/links (published) to enumerate every story slug.
  2. Filtered slugs become routes in the per-theme sitemap. Stories under landing-pages, legal-pages, campaigns, podcast, multiplier-pages, b2b-pages are emitted using their real_path; others use a slug-based path.
  3. At request time, the catch-all page in pages/_dynamicPage/ calls useStoryblokApi().getStory(...) to fetch the story, commits it to the storyblok Vuex module, and renders <component :is="blok.component"> for each body block. Component name in Storyblok must match a globally registered Vue component name.
  4. Layout (default vs minimal) and the impact banner visibility are story-controlled via Vuex mutations from inside the page setup.
  5. For preview mode, pages/storyblok.vue uses version: 'draft' and useStoryblokBridge for live editing.

Filtering and exclusions

The build-time slug list filters out:

  • Folders (is_folder)
  • Unpublished stories
  • Anything starting with redirects/ or global/
  • When generating French sitemap routes: stories under multiplier-pages, b2b-pages, podcast, warum-meditation-lernen

Magazin slugs are rewritten (magazin/demagazin, magazin/en/en/magazine/) so the URLs match the configured i18n routing.

What agents should know

  • Adding a new Storyblok component requires (a) creating the component in Storyblok with the correct technical name, and (b) registering a Vue component with the same name globally so <component :is> can resolve it. Most live in components/storyblok/.
  • Changing the Storyblok slug structure can break the sitemap generator. The folder-prefix conventions in fetchStoryblokPages are load-bearing.
  • The access token is the same for staging and production. There is no separate staging Storyblok space; published vs draft is the only environment-like distinction.
  • Storyblok is a runtime dependency. If Storyblok is unreachable, dynamic pages will fail to render. The build-time link fetch is wrapped in a try/catch and falls through with no routes if it fails.
  • For multi-brand work, the same Storyblok space is used. Brand-specific content uses stories under brand-specific folders or filters in the consumer code; Storyblok does not multi-tenant.