Skip to content

Repository files navigation

Portfolio – Chris Leon Noltemeier

A minimal, bilingual (DE/EN) Nuxt 3 portfolio with locale-based routing, SEO, and accessibility optimizations.

Setup & Development

  • Node: Use Node 22.x (see engines in package.json).
  • Install: npm install
  • Dev: npm run dev — runs the app at http://localhost:3000. Visiting / redirects to /de or /en based on your preferred locale.
  • Build: npm run build
  • Preview: npm run preview — serves the production build locally.

Environment Variables

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.

Content & i18n

Routing

  • Locale is part of the URL: /:locale (e.g. /de, /en).
  • The useI18n composable (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 in localStorage.
  • Global middleware in middleware/locale.global.ts redirects requests without a valid locale to the preferred locale (from localStorage or browser language, fallback en).

Adding or editing content

  • Translations: Edit data/i18n/de.ts and data/i18n/en.ts. Types are in types/i18n.ts; keep keys in sync across both files.
  • Profile (name, social links): data/profile.ts. Types in types/content.ts.
  • Projects (case studies): data/projects.ts. Each project has translations.de and translations.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, and now.

New projects or experience entries are added in the corresponding data files and, where needed, in the i18n namespaces (e.g. labels, section titles).

Deployment (Vercel)

  • Connect the repo to Vercel. Use the Nuxt preset (build command and output are set automatically). Otherwise: Build Command npm run build and the output directory from the Nuxt preset.
  • Set Environment Variables in the Vercel project: at least NUXT_PUBLIC_SITE_URL for production; add the contact-form variables listed above (see CONTACT_FORM_SETUP.md). Names must match Environment Variables above.
  • Production: Set NUXT_PUBLIC_SITE_URL to your canonical URL (e.g. https://chrisnoltemeier.de) so sitemap, robots.txt, and OG URLs are correct.
  • Preview deployments: If NUXT_PUBLIC_SITE_URL is not set, the app uses the preview URL as origin for meta and sitemap.

SEO & technical notes

  • Meta tags: Locale-specific title, description, html lang, and Open Graph / Twitter tags are set in pages/[locale]/index.vue via useSeoMeta() and useHead(), using the seo block from data/i18n/de.ts and data/i18n/en.ts. Optional seo.ogImage (path or full URL) can be set in i18n for social share images.
  • Sitemap: Served at /sitemap.xml (generated by server/routes/sitemap.xml.get.ts), listing /de and /en with lastmod and changefreq.
  • Robots: Served at /robots.txt (generated by server/routes/robots.txt.get.ts), allowing all crawlers and pointing to the sitemap.
  • Accessibility: One h1 in the hero; sections use h2/h3. Links, buttons, and focusable elements have visible focus rings (focus-visible). Nav and language toggle use aria-labels from i18n; the contact form status uses an aria-live region. Images use loading="lazy", decoding="async", and descriptive alt text. Particle animation is reduced on small viewports for performance and motion sensitivity.

About

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages