A minimal, bilingual (DE/EN) Nuxt 3 portfolio with locale-based routing, SEO, and accessibility optimizations.
- Node: Use Node 22.x (see
enginesinpackage.json). - Install:
npm install - Dev:
npm run dev— runs the app athttp://localhost:3000. Visiting/redirects to/deor/enbased on your preferred locale. - Build:
npm run build - Preview:
npm run preview— serves the production build locally.
Configure these in .env.local (or in your host’s dashboard, e.g. Vercel). See also .env.example and CONTACT_FORM_SETUP.md.
| Variable | Description |
|---|---|
NUXT_PUBLIC_SITE_URL |
Full public URL of the site (e.g. https://chrisnoltemeier.de). Used for sitemap, robots.txt, and canonical/OG URLs when the request host is not reliable (e.g. serverless). Optional; falls back to request host. |
Contact form (Resend + Cloudflare Turnstile):
| Variable | Description |
|---|---|
RESEND_API_KEY |
Resend API key (server-only). |
CONTACT_TO_EMAIL |
Inbox for submissions (e.g. chris.noltemeier@gmail.com). |
CONTACT_FROM_EMAIL |
Verified Resend sender address. |
TURNSTILE_SECRET_KEY |
Cloudflare Turnstile secret key (server-only). |
NUXT_PUBLIC_TURNSTILE_SITE_KEY |
Cloudflare Turnstile site key (public). |
NEXT_PUBLIC_TURNSTILE_SITE_KEY |
Optional alias for the site key. |
Full setup steps: CONTACT_FORM_SETUP.md.
- Locale is part of the URL:
/:locale(e.g./de,/en). - The
useI18ncomposable (composables/useI18n.ts) provides:locale— current locale ("de"or"en").messages— typed translations for the current locale.setLocale(newLocale)— switches locale and persists the choice inlocalStorage.
- Global middleware in
middleware/locale.global.tsredirects requests without a validlocaleto the preferred locale (fromlocalStorageor browser language, fallbacken).
- Translations: Edit
data/i18n/de.tsanddata/i18n/en.ts. Types are intypes/i18n.ts; keep keys in sync across both files. - Profile (name, social links):
data/profile.ts. Types intypes/content.ts. - Projects (case studies):
data/projects.ts. Each project hastranslations.deandtranslations.en; add or adjust entries there. - Experience & education:
data/experience.ts— structure and translations per locale. - Impact, Automation & AI, Now: Copy lives in the i18n files under
impact,automationAi, andnow.
New projects or experience entries are added in the corresponding data files and, where needed, in the i18n namespaces (e.g. labels, section titles).
- Connect the repo to Vercel. Use the Nuxt preset (build command and output are set automatically). Otherwise: Build Command
npm run buildand the output directory from the Nuxt preset. - Set Environment Variables in the Vercel project: at least
NUXT_PUBLIC_SITE_URLfor production; add the contact-form variables listed above (seeCONTACT_FORM_SETUP.md). Names must match Environment Variables above. - Production: Set
NUXT_PUBLIC_SITE_URLto your canonical URL (e.g.https://chrisnoltemeier.de) so sitemap,robots.txt, and OG URLs are correct. - Preview deployments: If
NUXT_PUBLIC_SITE_URLis not set, the app uses the preview URL as origin for meta and sitemap.
- Meta tags: Locale-specific title, description,
htmllang, and Open Graph / Twitter tags are set inpages/[locale]/index.vueviauseSeoMeta()anduseHead(), using theseoblock fromdata/i18n/de.tsanddata/i18n/en.ts. Optionalseo.ogImage(path or full URL) can be set in i18n for social share images. - Sitemap: Served at
/sitemap.xml(generated byserver/routes/sitemap.xml.get.ts), listing/deand/enwithlastmodandchangefreq. - Robots: Served at
/robots.txt(generated byserver/routes/robots.txt.get.ts), allowing all crawlers and pointing to the sitemap. - Accessibility: One
h1in the hero; sections useh2/h3. Links, buttons, and focusable elements have visible focus rings (focus-visible). Nav and language toggle usearia-labels from i18n; the contact form status uses anaria-liveregion. Images useloading="lazy",decoding="async", and descriptivealttext. Particle animation is reduced on small viewports for performance and motion sensitivity.