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 (or in your host’s dashboard, e.g. Vercel).
| Variable | Description |
|---|---|
NUXT_PUBLIC_SITE_URL |
Full public URL of the site (e.g. https://chrisnoltemeier.dev). 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_API_KEY— API key for Resend (or use another provider supported by the API route).CONTACT_TO_EMAIL— Address where contact form submissions are sent.CONTACT_FROM_EMAIL— Sender address used by the mail provider.- Optional:
FORMSPREE_ENDPOINT— If set, form submissions are proxied to Formspree instead of sending mail directly (useful if no mail provider is configured).
- CVs are served from the
publicfolder. Paths are defined indata/profile.tsunderprofile.cv:en:/cv/chris-leon-noltemeier-en.pdfde:/cv/chris-leon-noltemeier-de.pdf
- To update: Place your PDFs in
public/cv/with the same file names, or change the paths indata/profile.tsand add the corresponding files topublic/cv/. - No code change is required if you keep the same file names; just replace the files in
public/cv/.
- 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, CV paths, 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; addRESEND_API_KEY,CONTACT_TO_EMAIL, andCONTACT_FROM_EMAIL(orFORMSPREE_ENDPOINT) for the contact form. Names must match Environment Variables above. - Production: Set
NUXT_PUBLIC_SITE_URLto your canonical URL (e.g.https://chrisnoltemeier.dev) 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.