Summary: The 7Mind and 7Sleep web presence (marketing site + browser web app) — a single Nuxt 2 SSR codebase that builds into either brand via a THEME env var and deploys to GKE. Sources: direct code inspection (package.json, nuxt.config.ts, Dockerfile, DEPLOYMENT.md, .github/workflows/, terraform/web-v2/, plugins/, middleware/, config/routes.js) Last updated: 2026-05-15


Purpose

This repo is the public web presence and browser-accessible product for both 7Mind (meditation) and 7Sleep (sleep). One Nuxt codebase produces two distinct builds via the THEME=7mind or THEME=7sleep build arg. It covers:

  • SEO-critical marketing pages (sourced from Storyblok)
  • Magazin and podcast pages
  • Multi-country localized marketing (de, fr, nl, en, it, es, uk, us, ca for 7mind; de-only for 7sleep)
  • Subscription/checkout flows (Chargebee, legacy Fastspring)
  • Insurance reimbursement flows (krankenkasse, Barmer, DAK, Charité)
  • Coupon and gift flows
  • Auth (cookie/JWT login via supertokens-auth) for the web app surface
  • A web app account area (/account, /me, /login, /payments)
  • A cookie banner exported as a reusable web component (custom element) for consumption by other apps

It is a consumer of the elixir-backend HTTP API.

Stack

  • Nuxt 2.16 (Vue 2, SSR via nuxt start)
  • TypeScript (config + select files), JavaScript/Vue elsewhere
  • Node 18.19.0 (see .tool-versions, Dockerfile uses node:18-alpine)
  • Yarn 1 package manager
  • SCSS / Sass 1.32 with per-theme variables under assets/scss/{7mind,7sleep}
  • style-dictionary to compile design tokens from tokens/{7mind,7sleep}/*.json (custom Nuxt module modules/design-tokens.js)
  • Storyblok (@storyblok/nuxt-2) as the headless CMS for marketing pages, magazin, redirects
  • @nuxtjs/auth + supertokens-web-js 0.6 for client-side auth (7mind theme only; 7sleep has auth: false)
  • @nuxtjs/sentry for error tracking (separate Sentry projects per brand)
  • @nuxtjs/sitemap with dynamic per-theme sitemap generation
  • @nuxtjs/gtm Google Tag Manager
  • @nuxtjs/recaptcha v2
  • @nuxtjs/proxy to forward /user/**, /prevention/**, /monetization/**, /user_identity/**, /analytics/** to the backend (local dev / 8mind hosts only)
  • dd-trace Datadog APM, initialized as a Nuxt module
  • configcat-js-ssr for feature flags (4-min auto-poll, EU-only data governance)
  • chargebee JS lib + custom dialog (ChargebeeDialog.vue)
  • @mailchimp/mailchimp_marketing for newsletter signup (two list accounts)
  • swiper / vue-awesome-swiper, vue-lazyload, vue-mq for UI
  • nuxt-i18n (a fork at github.com/nikolashaag/i18n-module.git) supporting multiple locales on different domains
  • jest (unit), cypress (e2e against www.6mind.de / www.6sleep.de), storybook (linked from sibling 7mind-storybook repo in dev)

Key Files / Entry Points

  • nuxt.config.ts — the master config; reads THEME to branch everything (auth on/off, locales, baseUrl, recaptcha keys, sentry DSN, axios target, sitemap shape)
  • theme-helpers.js + themes.json — registry of theme names and their SCSS variable paths
  • config/routes.jsAPIRoutes constants. Single source of truth for backend endpoints called from the web (/user/signin, /monetization/subscriptions/status, /prevention/certificates, /insurances/{barmer,dak,charite,generic}/activate, etc.)
  • pages/_dynamicPage/ — catch-all routes that resolve to Storyblok stories (landing, legal, subroutes)
  • pages/storyblok.vue — preview/draft Storyblok renderer
  • middleware/healthcheck.ts/healthcheck server middleware used as K8s liveness/readiness probe (returns {"status":"ready"})
  • middleware/newslettersignup.js, middleware/mailchimp.js — server middleware proxying to Braze and Mailchimp
  • middleware/recaptcha.js — server middleware that verifies reCAPTCHA tokens against Google
  • middleware/redirects.js — declarative redirect map (resolved before render)
  • middleware/trailingSlashRedirect.js, middleware/lowercase.js — URL normalization
  • plugins/auth.js — extends @nuxtjs/auth user object with JWT decoding and isAppleGeneratedEmail derivation
  • plugins/axios-config.jsimportant: rewrites the backend hostname at request time to match the current page’s hostname (7mind.de6mind.de8mind.de family) so cookies and CORS stay aligned across env-equivalent domain pairs. See multi-domain-hostname-rewriting.
  • plugins/feature-toggle.js — ConfigCat client with URL query overrides
  • plugins/amplitude.client.ts, plugins/anonTracking.ts — client analytics
  • plugins/design-tokens.js, modules/design-tokens.js — style-dictionary build step
  • plugins/global.js, plugins/components.js — Vue component registration glue (includes Storybook components linked from sibling repo)
  • store/ — Vuex modules (auth, domains, storyblok, redirects, consents, skucoupon, globaltracking, abcampaigns)
  • components/krankenkasse/insurances.json, insurances1year.json — list of German health insurers used to generate sitemap routes
  • tokens/{7mind,7sleep}/*.json + tokens/dictionary.config.js — design tokens compiled at build time
  • lang/{7mind,7sleep}/*.js — i18n message files per brand and locale

Deployment

Target: GKE cluster application-cluster in GCP project sevenmind-infrastructure, region europe-west3. Routed through Traefik ingress with middleware app-7mind@file.

Image registry: europe-west3-docker.pkg.dev/sevenmind-infrastructure/application-cluster-repo/web-v2. Image tag = git commit SHA.

Environments:

  • Staging — namespace staging, 1 replica, domain www.6mind.de (THEME=7mind, sentry env staging)
  • Production — namespace production, 2 replicas (HPA min 2, max 6, CPU target 70%), 1Gi memory limit, domain www.7mind.de (THEME=7mind, sentry env production)

The container exposes port 3000 (Nuxt SSR); the K8s Service exposes port 80. Liveness and readiness probes both hit /healthcheck.

Terraform lives in this repo under terraform/web-v2/:

  • modules/web-v2/ defines Deployment, Service, Ingress, ConfigMap
  • environment/{staging,production}/main.tf instantiates the module
  • State backend: GCS bucket 7mind-terraform, prefix state/web-v2/{staging|production}
  • Terraform 1.12.1

Workflows (.github/workflows/):

  • ci.yml — runs jest + eslint inside the Docker test stage on every push
  • _build-and-deploy.yml — reusable workflow: builds the Docker production stage with THEME=7mind, pushes to Artifact Registry tagged <sha> and latest, then terraform plan/apply against the target environment, then Slack notification
  • deploy-production.yml — runs on push to master, calls _build-and-deploy.yml with environment=production. Merging master deploys straight to production with no manual approval.
  • deploy-from-command.yml — triggered by repository_dispatch: deploy-command, dispatched from slash-command-dispatch.yml. PR comment /deploy staging or /deploy production lets you ship a PR branch to either env before merging
  • terraform-cd-web-v2.yml — auto-runs terraform apply against staging when terraform/web-v2/** changes on master
  • terraform-deploy-web-v2.yml — manual workflow_dispatch for Terraform-only re-applies
  • manual-deploy.yml — manual workflow_dispatch for full build + deploy
  • deploy-feature-branch.yml — PR previews to *.review.6mind.de via legacy eu.gcr.io/mind-f62c0 registry (older pipeline, still active)
  • deploy-from-merge-legacy.yml — legacy push-to-master flow that builds both web-v2 and web-sleep images to eu.gcr.io/mind-f62c0. Currently coexists with deploy-production.yml. The legacy pipeline still ships the 7sleep image to the old infrastructure.
  • cleanup-feature-branch-on-merge.yml — removes the preview namespace after a PR closes
  • Sentry.yml — on deployment event, creates a Sentry release, attaches commits, finalizes on production
  • broken-link-checker.yml — weekly cron crawl of https://www.7mind.de
  • deployment-finalize.yml — TBD (post-deploy finalization)
  • deploy-feature-branch-7sleep.yml — entirely commented out; no 7sleep PR previews currently run

See DEPLOYMENT.md for the full operator runbook (slash commands, rollback, troubleshooting, kubectl reference).

Secrets

Application secrets are not managed in this repo. They live in a separate infrastructure repo under infrastructure/{staging,production}/web-v2/secrets.tf, where Terraform reads a gitignored terraform.tfvars and creates the K8s secrets web-v2-{env}-secrets. The web-v2 Deployment mounts those secrets via env_from.secret_ref. GitHub Actions only sees a small set of build-time tokens (NPM_TOKEN, NUXT_ENV_STORYBLOK_TOKEN, GCLOUD_AUTH_NEW, SLACK_WEBHOOK). Runtime values (Braze, GTM, Chargebee, ConfigCat, Mailchimp, reCAPTCHA, Sentry runtime token, Facebook App ID) come from K8s secrets only. See terraform/web-v2/SECRETS.md.

Dependencies

External services (with notes on configuration):

  • Storyblok — headless CMS. One space serves both brands; access token in NUXT_ENV_STORYBLOK_TOKEN. Sitemap routes, redirects, landing pages, and the magazin are all sourced from Storyblok at build time and via useStoryblokApi() at request time. Memory cache provider.
  • SuperTokens — auth via supertokens-web-js. JWTs validated server-side by elixir-backend. See supertokens-auth.
  • Chargebee — subscriptions and checkout. Brand-specific namespace via NUXT_ENV_CHARGEBEE_ID (7mind prod, 7mind-test staging). See chargebee-billing.
  • Fastspring — legacy billing surfaced via NUXT_ENV_FASTSPRING_STOREFRONT; still referenced in checkout components.
  • Sentry — separate projects per brand under org 7mind-gmbh (7mind-web-v2, 7sleep-web-v2). DSN hardcoded in nuxt.config.ts. Release tagging done by Sentry.yml workflow.
  • Datadog APMdd-trace init at module load; pod labels tags.datadoghq.com/env, service, version set from Terraform.
  • Amplitude — client-side analytics. NUXT_ENV_AMPLITUDE_ANALYTICS_KEY. Also hooks into a global experimentIntegration user/device id.
  • Braze — push/email targeting. Two API keys: enduser (NUXT_ENV_BRAZE_API_KEY) and B2B (NUXT_ENV_BRAZE_B2B_API_KEY). Subscription group IDs are hardcoded per brand/env in middleware/newslettersignup.js.
  • Google Tag ManagerNUXT_ENV_GTM_ID + NUXT_ENV_GTM_AUTH + NUXT_ENV_GTM_PREVIEW composed into one ID at runtime (workaround for nuxt-community/gtm-module issue 94).
  • ConfigCat — feature flags. Polls every 240s with dataGovernance: 'EuOnly'. Per-session identifier via 7mind-session-id cookie. URL query (?<flagKey>=true|false) overrides the value for testing.
  • Mailchimp — newsletter. Two separate accounts: unternehmen on server us14 (B2B), default on us9 (B2C). API keys MAILCHIMP_API_KEY and MAILCHIMP_API_KEY_2.
  • Google reCAPTCHA v2 — site key per theme is hardcoded in nuxt.config.ts; secret per theme via NUXT_ENV_RECAPTCHA_API_KEY{,_7SLEEP} and verified server-side in middleware/recaptcha.js.
  • Google Fonts — Lora 400/700 for the 7mind theme only.
  • Facebook App — login + share; NUXT_ENV_FACEBOOK_APP_ID (different IDs per env).
  • Apple Sign-in App IDNUXT_ENV_APPLE_APP_ID (de.7mind.www prod, de.6mind.www staging).
  • Google OAuth — client ID per env via NUXT_ENV_GOOGLE_CLIENT_ID.

Internal:

  • HTTP consumer of elixir-backend Public API. In prod points at https://magic.7mind.de (7mind) or https://magic.7sleep.de (7sleep). Staging is magic.6mind.de / magic.6sleep.de. Identity service at id.7mind.de. Routes called are enumerated in config/routes.js and follow the same /user/*, /monetization/*, /prevention/*, /user_identity/*, /insurances/* namespaces as modulith-domain-model.
  • Consumes @sevenmind/* packages from the GitHub Packages npm registry (npm.pkg.github.com). NPM_TOKEN (a GitHub PAT) is required at install time both locally and in CI.
  • The sibling 7mind-storybook repo provides the design system component library. In local dev it is consumed from a sibling directory (../7mind-storybook) and linked into the container; in production it is consumed as a published @sevenmind npm package. The Storybook components are loaded at runtime by plugins/components.js and registered globally.
  • The cookie banner is built as a Nuxt custom-element (/_nuxt/scripts/cookie-banner/cookie-banner.js) and consumed by other 7Mind frontends.

Integration Points

  • End users — browsers on www.7mind.de, www.7sleep.de, plus localized TLDs (.app, .ca, .co.uk, .es, .fr, .it, .nl, .uk) and a www2.7mind.de migration host. Plus *.review.6mind.de for PR previews.
  • elixir-backend — every authenticated and most public API calls go to the backend Public API. Login establishes a cookie that the backend validates as a SuperTokens session.
  • Other 7Mind frontends — consume the cookie-banner custom element bundle.
  • Sentry / Datadog / Storyblok / Braze / Mailchimp / GTM / Chargebee dashboards — the receivers of the runtime data this app emits.
  • CDN / static assets — served by Nuxt SSR directly through the K8s Service; no separate CDN configured in this repo. Cloudflare sits in front of the public domains (see cloudflare).

Conventions

  • THEME env var drives everything. Build-time arg in the Dockerfile, runtime config in nuxt.config.ts. Every config dimension (auth on/off, locales, recaptcha keys, sentry DSN, sitemap shape, backend URL, favicon path, css variables) branches on it. Never hardcode brand-specific values in components; thread through $config.theme or read process.env.THEME at build time.
  • Hostname rewriting at request time. plugins/axios-config.js rewrites the backend baseURL and any absolute URL so that [67]mind.<tld> becomes the current request’s [6789]mind.<tld>. This is what lets the same Nuxt build serve 7mind.de, 6mind.de (staging), 8mind.de (local dev with /etc/hosts), and the 9mind review domains while keeping cookies host-only. Don’t bypass this by hardcoding a host. See multi-domain-hostname-rewriting.
  • i18n uses differentDomains: true. Each locale lives on a different country TLD (.de, .fr, .nl, .it, .es, etc.) rather than a path prefix. Adding a new locale means coordinating with infra to map the new TLD to the same ingress. English is the only locale that uses a path prefix (/en).
  • Sitemap is dynamic and per-theme. Generated from Storyblok pages plus a programmatically expanded list of /krankenkasse/<course>/<insurance> routes (4 courses × ~100 insurances). When adding insurance/course combos, edit components/krankenkasse/insurances{,1year}.json.
  • Two coexisting deploy pipelines. Old (deploy-from-merge-legacy.yml, registry eu.gcr.io/mind-f62c0, Helm under k8s/base/) and new (deploy-production.yml, registry europe-west3-docker.pkg.dev, Terraform under terraform/web-v2/). Production traffic is served by the new pipeline (GKE application-cluster). The legacy flow still builds and pushes the 7sleep image. Don’t assume one pipeline; check both before making infra changes.
  • master → production directly. Merging to master triggers deploy-production.yml with no manual gate. Use /deploy staging on the PR first to validate (see DEPLOYMENT.md).
  • Image tag is the commit SHA. This is the contract between this repo, the docker push, and the Terraform app_version variable. Don’t deploy latest for production.
  • NPM_TOKEN is required for install because of the @sevenmind private GitHub packages. Set it before yarn install locally or builds will fail with 401.
  • Container port 3000, K8s service port 80, local dev port 80. The Nuxt dev script binds Nuxt to port 80 directly via --port 80, requiring sudo or a root-capable host (the Docker dev image handles this).
  • Healthcheck endpoint is /healthcheck via serverMiddleware, not /health. K8s liveness/readiness both call it.
  • Cookie banner is a custom element, not a regular Vue component when consumed externally. Its source lives in the sibling storybook repo (@/storybook/src/sections/cookiebanner/SMMCookieBanner.vue) and is exported under the tag cookie-banner-sevenmind.
  • Storyblok pages can be excluded from build output by prefixing slugs with redirects/ or global/, or by marking them as folders/unpublished. The sitemap builder also excludes paths starting with landing-pages, legal-pages, campaigns, podcast, multiplier-pages, b2b-pages from default slug treatment and uses real_path for them.
  • ConfigCat flags can be overridden via URL query (?<flagKey>=true) for testing without touching the dashboard.
  • The 7sleep production deployment is currently the legacy path, not Terraform. The new Terraform module is wired for THEME=7mind only (see build-args: THEME=7mind in _build-and-deploy.yml). Adding 7sleep to the new pipeline requires either a second module instance or a second build matrix.
  • Cypress runs against the staging domains by default (www.6mind.de, www.6sleep.de). Local Cypress requires /etc/hosts entries for 8mind.de / 8mind.fr for the French locale.
  • The fork of nuxt-i18n lives at github.com/nikolashaag/i18n-module.git — multiple locales on the same domain are not supported in the upstream module. Replacing the dep without keeping that feature would break the /en prefix locales.

Agent Change Log

2026-05-15 — initial knowledge extraction from codebase