Construire un portfolio open-source moderne avec Nuxt 3 — thèmes CSS, données JSON, SEO automatique, et déploiement Docker.
Nuxt 3 génère automatiquement les routes depuis le dossier pages/. Pas de fichier de config de router — chaque fichier .vue devient une URL.
pages/
├── index.vue → /
├── projets.vue → /projets
└── contact.vue → /contactC'est l'un des avantages de Nuxt face à Vue Router nu : zéro configuration. Ajouter une page = créer un fichier.
<template>
<div>
<Header />
<NuxtPage />
<Footer />
</div>
</template><NuxtPage /> est le slot où Nuxt injecte le composant de la page courante. Header et Footer sont rendus sur toutes les pages automatiquement.
Nuxt 3 importe automatiquement tout ce qui est dans components/. Pas d'import manuel dans les fichiers .vue :
<template>
<!-- Header est importé automatiquement — aucun import nécessaire -->
<Header />
<main>...</main>
</template>Même chose pour les composables dans composables/ : useJson(), useHead(), useSeoMeta() — tous disponibles sans import.
Nuxt 3 fait du SSR (Server-Side Rendering) par défaut. Chaque page est rendue côté serveur au premier chargement, puis hydratée côté client.
Pour OpenFolio, déployé en statique sur Vercel ou Docker, Nuxt génère du HTML pré-rendu. Les bots SEO voient le contenu complet immédiatement.
export default defineNuxtConfig({
nitro: {
preset: 'node-server', // pour Docker
},
})Changer le preset en 'static' génère un site 100% statique avec nuxt generate. Utile pour Netlify ou GitHub Pages.
Un piège courant avec Nuxt 3 : le code s'exécute deux fois — une fois sur le serveur, une fois sur le client.
<script setup lang="ts">
// S'exécute serveur + client
const { data } = await useJson<Profile>('/data/profile.json')
// S'exécute client seulement
onMounted(() => {
console.log('rendu côté client')
})
</script>window, document, navigator — toujours dans onMounted ou derrière une vérification import.meta.client. Sur le serveur, ces APIs n'existent pas.
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: true },
modules: ['@nuxtjs/tailwindcss', 'nuxt-icon'],
css: ['~/assets/themes/ocean.css'],
nitro: {
preset: 'node-server',
routeRules: {
'/data/**': { headers: { 'cache-control': 'public, max-age=300' } },
},
},
app: {
head: {
title: 'Portfolio Open Source',
meta: [{ name: 'description', content: 'administrable via JSON' }],
},
},
})routeRules sur /data/** met les JSON en cache 5 minutes côté client et CDN — les requêtes de données répétées ne touchent pas le serveur.