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 usesnode: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 modulemodules/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 sibling7mind-storybookrepo in dev)
Key Files / Entry Points
nuxt.config.ts— the master config; readsTHEMEto 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 pathsconfig/routes.js—APIRoutesconstants. 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 renderermiddleware/healthcheck.ts—/healthcheckserver middleware used as K8s liveness/readiness probe (returns{"status":"ready"})middleware/newslettersignup.js,middleware/mailchimp.js— server middleware proxying to Braze and Mailchimpmiddleware/recaptcha.js— server middleware that verifies reCAPTCHA tokens against Googlemiddleware/redirects.js— declarative redirect map (resolved before render)middleware/trailingSlashRedirect.js,middleware/lowercase.js— URL normalizationplugins/auth.js— extends@nuxtjs/authuser object with JWT decoding andisAppleGeneratedEmailderivationplugins/axios-config.js— important: rewrites the backend hostname at request time to match the current page’s hostname (7mind.de→6mind.de→8mind.defamily) 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 overridesplugins/amplitude.client.ts,plugins/anonTracking.ts— client analyticsplugins/design-tokens.js,modules/design-tokens.js— style-dictionary build stepplugins/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 routestokens/{7mind,7sleep}/*.json+tokens/dictionary.config.js— design tokens compiled at build timelang/{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, domainwww.6mind.de(THEME=7mind, sentry envstaging) - Production — namespace
production, 2 replicas (HPA min 2, max 6, CPU target 70%),1Gimemory limit, domainwww.7mind.de(THEME=7mind, sentry envproduction)
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, ConfigMapenvironment/{staging,production}/main.tfinstantiates the module- State backend: GCS bucket
7mind-terraform, prefixstate/web-v2/{staging|production} - Terraform 1.12.1
Workflows (.github/workflows/):
ci.yml— runs jest + eslint inside the Dockerteststage on every push_build-and-deploy.yml— reusable workflow: builds the Dockerproductionstage withTHEME=7mind, pushes to Artifact Registry tagged<sha>andlatest, thenterraform plan/applyagainst the target environment, then Slack notificationdeploy-production.yml— runs on push tomaster, calls_build-and-deploy.ymlwithenvironment=production. Merging master deploys straight to production with no manual approval.deploy-from-command.yml— triggered byrepository_dispatch: deploy-command, dispatched fromslash-command-dispatch.yml. PR comment/deploy stagingor/deploy productionlets you ship a PR branch to either env before mergingterraform-cd-web-v2.yml— auto-runsterraform applyagainst staging whenterraform/web-v2/**changes on masterterraform-deploy-web-v2.yml— manualworkflow_dispatchfor Terraform-only re-appliesmanual-deploy.yml— manualworkflow_dispatchfor full build + deploydeploy-feature-branch.yml— PR previews to*.review.6mind.devia legacyeu.gcr.io/mind-f62c0registry (older pipeline, still active)deploy-from-merge-legacy.yml— legacy push-to-master flow that builds bothweb-v2andweb-sleepimages toeu.gcr.io/mind-f62c0. Currently coexists withdeploy-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 closesSentry.yml— ondeploymentevent, creates a Sentry release, attaches commits, finalizes on productionbroken-link-checker.yml— weekly cron crawl ofhttps://www.7mind.dedeployment-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 viauseStoryblokApi()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(7mindprod,7mind-teststaging). 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 innuxt.config.ts. Release tagging done bySentry.ymlworkflow. - Datadog APM —
dd-traceinit at module load; pod labelstags.datadoghq.com/env,service,versionset from Terraform. - Amplitude — client-side analytics.
NUXT_ENV_AMPLITUDE_ANALYTICS_KEY. Also hooks into a globalexperimentIntegrationuser/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 inmiddleware/newslettersignup.js. - Google Tag Manager —
NUXT_ENV_GTM_ID+NUXT_ENV_GTM_AUTH+NUXT_ENV_GTM_PREVIEWcomposed 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 via7mind-session-idcookie. URL query (?<flagKey>=true|false) overrides the value for testing. - Mailchimp — newsletter. Two separate accounts:
unternehmenon serverus14(B2B), default onus9(B2C). API keysMAILCHIMP_API_KEYandMAILCHIMP_API_KEY_2. - Google reCAPTCHA v2 — site key per theme is hardcoded in
nuxt.config.ts; secret per theme viaNUXT_ENV_RECAPTCHA_API_KEY{,_7SLEEP}and verified server-side inmiddleware/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 ID —
NUXT_ENV_APPLE_APP_ID(de.7mind.wwwprod,de.6mind.wwwstaging). - 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) orhttps://magic.7sleep.de(7sleep). Staging ismagic.6mind.de/magic.6sleep.de. Identity service atid.7mind.de. Routes called are enumerated inconfig/routes.jsand 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@sevenmindnpm package. The Storybook components are loaded at runtime byplugins/components.jsand 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 awww2.7mind.demigration host. Plus*.review.6mind.defor 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
THEMEenv var drives everything. Build-time arg in the Dockerfile, runtime config innuxt.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.themeor readprocess.env.THEMEat build time.- Hostname rewriting at request time.
plugins/axios-config.jsrewrites the backendbaseURLand any absolute URL so that[67]mind.<tld>becomes the current request’s[6789]mind.<tld>. This is what lets the same Nuxt build serve7mind.de,6mind.de(staging),8mind.de(local dev with/etc/hosts), and the9mindreview 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, editcomponents/krankenkasse/insurances{,1year}.json. - Two coexisting deploy pipelines. Old (
deploy-from-merge-legacy.yml, registryeu.gcr.io/mind-f62c0, Helm underk8s/base/) and new (deploy-production.yml, registryeurope-west3-docker.pkg.dev, Terraform underterraform/web-v2/). Production traffic is served by the new pipeline (GKEapplication-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 tomastertriggersdeploy-production.ymlwith no manual gate. Use/deploy stagingon the PR first to validate (seeDEPLOYMENT.md).- Image tag is the commit SHA. This is the contract between this repo, the docker push, and the Terraform
app_versionvariable. Don’t deploylatestfor production. NPM_TOKENis required for install because of the@sevenmindprivate GitHub packages. Set it beforeyarn installlocally 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
/healthcheckviaserverMiddleware, 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 tagcookie-banner-sevenmind. - Storyblok pages can be excluded from build output by prefixing slugs with
redirects/orglobal/, or by marking them as folders/unpublished. The sitemap builder also excludes paths starting withlanding-pages,legal-pages,campaigns,podcast,multiplier-pages,b2b-pagesfrom default slug treatment and usesreal_pathfor 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=7mindonly (seebuild-args: THEME=7mindin_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/hostsentries for8mind.de/8mind.frfor the French locale. - The fork of
nuxt-i18nlives atgithub.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/enprefix locales.
Agent Change Log
2026-05-15 — initial knowledge extraction from codebase