Files
portfolio/.planning/research/STACK.md
T
2026-04-07 23:17:32 +02:00

11 KiB

Technology Stack

Project: Portfolio Killian Dalcin — Nuxt 4 SSR Migration Researched: 2026-04-07 Knowledge cutoff: August 2025 — all versions marked LOW confidence must be verified against npm before pinning


IMPORTANT: Version Verification Required

All network tools were unavailable during this research session. Versions below are from training data (cutoff August 2025). Before starting the project, run:

npm info nuxt version
npm info @nuxt/ui version
npm info @nuxtjs/i18n version
npm info @nuxtjs/color-mode version
npm info @nuxtjs/sitemap version
npm info @nuxtjs/seo version
npm info nuxt-gtag version
npm info @pinia/nuxt version
npm info @nuxt/image version
npm info @nuxt/eslint version

Core Framework

Technology Version (training data) Confidence Purpose Why
nuxt ^4.0.0 LOW — verify on npm SSR framework Only reason this migration exists: per-route SSR so every page is crawlable without client JS. Nuxt 4 is the current stable major.
vue ^3.5.x MEDIUM UI layer Peer dependency of Nuxt 4; Vue 3.5 introduces useTemplateRef and improved reactivity — no action needed, Nuxt manages it
typescript ^5.x MEDIUM Type safety Nuxt 4 ships its own TS config; strict mode enforced via tsconfig.json extends
node 22.x LTS HIGH Runtime Matches Docker base image node:22-alpine; Node 22 is current LTS as of April 2026

UI & Styling

Technology Version (training data) Confidence Purpose Why
@nuxt/ui ^3.0.0 LOW — verify on npm Component library v3 is built on Tailwind v4 and Radix Vue, ships production-ready components (UModal, UForm, UInput, UTextarea). Replaces ~80% of custom component work. v2 is NOT compatible with Tailwind v4.
tailwindcss ^4.0.0 LOW — verify on npm Utility CSS Bundled as a dependency of @nuxt/ui v3; do NOT install separately or pin a conflicting version. Tailwind v4 ships as a Vite/PostCSS plugin, no tailwind.config.js needed.
@nuxtjs/color-mode ^3.5.x LOW — verify on npm Dark/light mode Nuxt-native module; writes a cookie on the server, so no FOUC and no hydration mismatch. localStorage alternative is explicitly broken for SSR. Must set storage: 'cookie' in config.

Internationalisation

Technology Version (training data) Confidence Purpose Why
@nuxtjs/i18n ^9.x LOW — verify on npm FR/EN i18n v9 is the Nuxt 4-compatible major. v8 targets Nuxt 3. Uses useCookie() for locale persistence (SSR-safe). Must set detectBrowserLanguage.cookieKey and cookieCrossOrigin appropriately; localStorage fallback must be disabled.
vue-i18n ^10.x LOW — peer dep Translation runtime Peer dep of @nuxtjs/i18n v9; do not install vue-i18n v9 (Nuxt 3 era).

SEO

Technology Version (training data) Confidence Purpose Why
@nuxtjs/sitemap ^6.x LOW — verify on npm sitemap.xml Auto-generates sitemap from Nuxt routes including i18n alternates. Required by the PROJECT.md spec. Must be configured with i18n option when @nuxtjs/i18n is present to emit hreflang entries.
@nuxtjs/seo ^2.x LOW — verify on npm SEO meta bundle Meta-module that installs and pre-configures @nuxtjs/sitemap, nuxt-og-image, nuxt-schema-org, nuxt-link-checker. Using it avoids duplicate sitemap config. If using @nuxtjs/seo, do NOT also install @nuxtjs/sitemap standalone (conflict risk). Choose one.

Decision needed: Use @nuxtjs/seo (meta-module, installs sitemap + og-image + schema-org) OR install @nuxtjs/sitemap standalone and useSeoMeta() manually. Recommendation: use @nuxtjs/seo because the portfolio needs og:image and JSON-LD (project requirement), and the meta-module wires them together with zero boilerplate.

Analytics

Technology Version (training data) Confidence Purpose Why
nuxt-gtag ^3.x LOW — verify on npm Google Analytics 4 Replaces GA4 hardcoded in index.html. Injects gtag.js via Nuxt's head management, respects SSR. Must be configured with id: 'G-XXXXXXXX' from runtimeConfig.public.

State Management

Technology Version (training data) Confidence Purpose Why
@pinia/nuxt ^0.9.x LOW — verify on npm Global state Required if any state needs to survive navigation (e.g., project filter state). For a portfolio with mostly static data this may be optional; include it anyway because Pinia integrates with Nuxt devtools and SSR hydration is handled automatically.
pinia ^3.x LOW — peer dep Pinia core Peer dep of @pinia/nuxt; version must match.

Images

Technology Version (training data) Confidence Purpose Why
@nuxt/image ^1.x LOW — verify on npm Optimised images <NuxtImg> replaces <img> for automatic lazy loading, srcset, and format conversion. Project requirement: lazy load project gallery images. Use provider: 'ipx' (built-in, no external service).

Developer Tooling

Technology Version (training data) Confidence Purpose Why
@nuxt/eslint ^0.7.x LOW — verify on npm ESLint + Prettier Nuxt-native flat config ESLint. Replaces manual eslint + prettier wiring. Enforces Vue 3 best practices. One module, one config file.

Infrastructure

Technology Version Confidence Purpose Why
Docker node:22-alpine 22-alpine HIGH Container base Alpine keeps image small (~50MB base). Node 22 matches the runtime. Multi-stage build: stage 1 installs deps + builds, stage 2 copies .output/ only.

Alternatives Considered

Category Recommended Alternative Why Not
UI library @nuxt/ui v3 Vuetify, PrimeVue, custom Nuxt UI v3 is Tailwind-native, ships ready for Nuxt 4, has UModal/UForm exactly as specced. Others require extra adapter work.
CSS Tailwind v4 (via @nuxt/ui) Tailwind v3, UnoCSS, plain CSS v4 is the current generation; UnoCSS is a valid alternative but adds config overhead with no benefit for this scope.
i18n @nuxtjs/i18n v9 lingui, custom composable @nuxtjs/i18n has Nuxt 4 SSR cookie support built-in; alternatives require manual SSR wiring.
Analytics nuxt-gtag Umami (self-hosted) Umami is out of scope per PROJECT.md. nuxt-gtag is the standard Nuxt-native GA4 module.
State @pinia/nuxt useState() only useState() is fine for simple per-component state but Pinia is needed for shared filter state across pages. Include it from day one to avoid a refactor.
CMS Static TS data files @nuxt/content PROJECT.md explicitly rules out @nuxt/content. Data is bilingual TS objects already; keep them.
Contact form backend EmailJS Custom API, Formspree No backend to maintain. EmailJS free tier is sufficient for a portfolio contact form. Not a Nuxt module — just an npm package (emailjs-com).
Sitemap + SEO meta @nuxtjs/seo (bundle) @nuxtjs/sitemap standalone @nuxtjs/seo includes og-image and schema-org which the project spec requires. One module is simpler.

What NOT to Use

Package Reason
vue-router (manual) Nuxt 4 ships file-based routing on top of vue-router; never import vue-router directly in a Nuxt project
@nuxt/content Explicitly out of scope; TS data files are simpler and already exist
localStorage for i18n/theme Not readable on server; causes hydration mismatch and FOUC. Use cookies only.
Tailwind v3 @nuxt/ui v3 requires Tailwind v4. Mixing versions breaks everything.
@nuxtjs/i18n v8 Only compatible with Nuxt 3. v9 is required for Nuxt 4.
nuxt generate (full SSG) May be considered for perf, but SSR is the core value of this migration (per PROJECT.md). Use nuxt build + node server in Docker. Revisit after launch if edge deployment is added.

nuxt.config.ts Skeleton

export default defineNuxtConfig({
  compatibilityDate: '2025-01-01',

  modules: [
    '@nuxt/ui',
    '@nuxtjs/i18n',
    '@nuxtjs/color-mode',
    '@nuxtjs/seo',       // includes sitemap, og-image, schema-org
    'nuxt-gtag',
    '@pinia/nuxt',
    '@nuxt/image',
    '@nuxt/eslint',
  ],

  colorMode: {
    preference: 'system',
    fallback: 'light',
    storage: 'cookie',   // SSR-safe: no FOUC
  },

  i18n: {
    locales: ['fr', 'en'],
    defaultLocale: 'fr',
    detectBrowserLanguage: {
      useCookie: true,
      cookieKey: 'i18n_redirected',
      redirectOn: 'root',
    },
  },

  gtag: {
    id: '', // set via runtimeConfig.public.gtag.id
  },

  image: {
    provider: 'ipx',
  },

  typescript: {
    strict: true,
  },
})

Docker Production Setup

# Stage 1: build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: runtime
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.output ./output
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["node", "output/server/index.mjs"]

Key points:

  • Only .output/ is copied to the final image. No node_modules/, no source files.
  • node:22-alpine is the project constraint (matches dev runtime).
  • Nuxt 4 SSR server entry is .output/server/index.mjs.

Installation

# Scaffold Nuxt 4 project
npx nuxi@latest init portfolio --template=v4-compat
cd portfolio

# Core modules
npm install @nuxt/ui @nuxtjs/i18n @nuxtjs/color-mode @nuxtjs/seo nuxt-gtag @pinia/nuxt pinia @nuxt/image

# Dev tooling
npm install -D @nuxt/eslint typescript

# Contact form (not a Nuxt module)
npm install @emailjs/browser

Confidence Summary

Area Confidence Notes
Nuxt 4 as framework MEDIUM Nuxt 4 was in RC/stable as of mid-2025; verify exact version on npm
@nuxt/ui v3 LOW v3 was in active development; confirm stable tag on npm
@nuxtjs/i18n v9 (Nuxt 4 compat) LOW v9 announced for Nuxt 4; confirm it's the latest dist-tag
@nuxtjs/color-mode cookie storage MEDIUM This feature existed in v3.3+; verify it persists in latest
@nuxtjs/seo as meta-bundle MEDIUM Module has been stable; inclusion of sitemap+og-image confirmed in v2 docs
nuxt-gtag LOW Verify v3 is compatible with Nuxt 4
@pinia/nuxt MEDIUM Pinia 3 + @pinia/nuxt 0.9 tracked Nuxt 4 compat closely
Docker node:22-alpine HIGH Node 22 is current LTS; Alpine variant is standard
EmailJS (non-Nuxt) HIGH Stable library, no Nuxt dependency

Sources

  • Training data (knowledge cutoff August 2025) — all external tools blocked during this research session
  • PROJECT.md constraints and requirements: .planning/PROJECT.md
  • npm registry verification required before pinning versions (see commands at top of this file)