iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Vue · Intermédiaire

OpenFolio

Construire un portfolio open-source moderne avec Nuxt 3 — thèmes CSS, données JSON, SEO automatique, et déploiement Docker.

Nuxt 3Vue 3TypeScriptTailwind CSSDocker
GitHub
01Setup et configuration02Structure et routing Nuxt 303Données JSON et composables04Système de thèmes CSS05Pages et composants06SEO et performances07Déploiement Docker et Vercel
Chapitre 3·25 min

Données JSON et composables

OpenFolio n'a pas de base de données. Tout le contenu vit dans /public/data/. Le composable useJson fait le pont entre ces fichiers et les composants Vue.

Le composable useJson

composables/useJson.ts
export function useJson<T>(url: string) {
  return useFetch<T>(url, {
    key: url,
    server: true,
    lazy: false,
  })
}

useFetch est le composable natif de Nuxt pour les requêtes HTTP. Il gère automatiquement :

  • La déduplication (deux composants qui demandent le même fichier = une seule requête)
  • L'hydratation serveur → client (les données fetchées côté serveur sont sérialisées et réutilisées côté client sans refetch)
  • Les états pending, data, error

Charger le profil

pages/index.vue
<script setup lang="ts">
interface Profile {
  name: string
  username: string
  title: string
  bio: string
  avatar: string
  available: boolean
  social: Array<{ type: string; url: string; label: string }>
}
 
const { data: profile, pending, error } = await useJson<Profile>('/data/profile.json')
</script>
 
<template>
  <div v-if="pending">Chargement...</div>
  <div v-else-if="error">Erreur de chargement</div>
  <div v-else>
    <h1>{{ profile?.name }}</h1>
    <p>{{ profile?.bio }}</p>
  </div>
</template>

Le await sur useJson bloque le rendu jusqu'à ce que les données soient disponibles. En SSR, ça signifie que le HTML envoyé au client contient déjà le profil — pas de flash de contenu vide.

Structure des projets

public/data/projects.json
[
  {
    "id": 1,
    "title": "E-Commerce Modern",
    "description": "Plateforme e-commerce avec Nuxt.js et Stripe",
    "category": "Web",
    "status": "completed",
    "technologies": ["Nuxt.js", "Stripe", "PostgreSQL"],
    "github": "https://github.com/...",
    "demo": "https://...",
    "image": "/images/projects/ecommerce.jpg",
    "featured": true,
    "metrics": {
      "duration": "3 months",
      "team": 2,
      "impact": "+40% ventes"
    }
  }
]

status peut être "completed", "in-progress", ou "planned" — la page projets affiche un badge coloré selon cette valeur.

Filtrage côté client

La page projets extrait dynamiquement les catégories uniques depuis les données :

pages/projets.vue
<script setup lang="ts">
const { data: projects } = await useJson<Project[]>('/data/projects.json')
 
const activeFilter = ref<string>('Tous')
 
const categories = computed(() =>
  ['Tous', ...new Set(projects.value?.map(p => p.category) ?? [])]
)
 
const filtered = computed(() =>
  activeFilter.value === 'Tous'
    ? projects.value ?? []
    : projects.value?.filter(p => p.category === activeFilter.value) ?? []
)
</script>

Ajouter une catégorie "Mobile" dans projects.json = elle apparaît automatiquement dans les filtres. Aucun code à modifier.

Ajouter une expérience

public/data/experiences.json
[
  {
    "company": "Startup XYZ",
    "role": "Développeur Frontend",
    "period": "2023 — 2025",
    "description": "Refonte complète de l'interface avec Vue 3.",
    "technologies": ["Vue 3", "TypeScript", "Figma"],
    "current": false
  }
]

current: true marque le poste actuel avec un indicateur visuel dans la timeline.

Ajouter une compétence

public/data/skills.json
[
  {
    "category": "Frontend",
    "items": [
      { "name": "Vue.js", "level": 90 },
      { "name": "TypeScript", "level": 80 },
      { "name": "Nuxt 3", "level": 85 }
    ]
  },
  {
    "category": "Backend",
    "items": [
      { "name": "Node.js", "level": 75 },
      { "name": "PostgreSQL", "level": 70 }
    ]
  }
]

level est une valeur entre 0 et 100 qui pilote la largeur de la barre de progression. 80 = "solide", 90+ = "expert".

Préchargement des données clés

nuxt.config.ts précharge les JSON critiques pour éviter un waterfall de requêtes :

nuxt.config.ts
nitro: {
  prerender: {
    crawlLinks: true,
  },
},
app: {
  head: {
    link: [
      { rel: 'preload', as: 'fetch', href: '/data/projects.json', crossorigin: 'anonymous' },
      { rel: 'preload', as: 'fetch', href: '/data/profile.json', crossorigin: 'anonymous' },
    ],
  },
},

Le navigateur commence à télécharger les JSON dès que le HTML arrive, avant même que Vue soit initialisé.

Précédent
Structure et routing Nuxt 3
Suivant
Système de thèmes CSS

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main