Frameworks

Nuxt 3 : Le Framework Fullstack pour Vue.js

Mark Toledo

Mark Toledo

3 janvier 2025

Nuxt 3 : Le Framework Fullstack pour Vue.js

Nuxt 3 est le framework fullstack de référence pour Vue.js. Il simplifie le développement d'applications universelles avec le rendu côté serveur (SSR), la génération statique, et une expérience développeur exceptionnelle.

Pourquoi Nuxt ?

Nuxt résout les défis courants du développement Vue.js :

  • SEO optimisé grâce au rendu serveur
  • Performance avec la génération statique
  • DX excellente : auto-imports, routing automatique
  • Fullstack : API routes intégrées
  • Déploiement flexible : serveur, statique, edge

Installation

npx nuxi@latest init mon-projet-nuxt
cd mon-projet-nuxt
npm install
npm run dev

Structure du projet

mon-projet-nuxt/
├── .nuxt/              # Build (généré)
├── assets/             # Styles, images (transformés par Vite)
├── components/         # Composants Vue (auto-importés)
├── composables/        # Composables (auto-importés)
├── layouts/            # Mises en page
├── middleware/         # Middleware de navigation
├── pages/              # Routes (générées automatiquement)
├── plugins/            # Plugins Vue
├── public/             # Fichiers statiques
├── server/             # API et middleware serveur
├── app.vue             # Composant racine
└── nuxt.config.ts      # Configuration

Routing automatique

Les fichiers dans pages/ génèrent automatiquement les routes :

pages/
├── index.vue           → /
├── about.vue           → /about
├── blog/
│   ├── index.vue       → /blog
│   └── [slug].vue      → /blog/:slug
└── users/
    └── [id]/
        └── profile.vue → /users/:id/profile

Page basique

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Accueil</h1>
    <NuxtLink to="/about">À propos</NuxtLink>
  </div>
</template>

Page dynamique

<!-- pages/blog/[slug].vue -->
<template>
  <article>
    <h1>{{ article.titre }}</h1>
    <div v-html="article.contenu"></div>
  </article>
</template>

<script setup>
const route = useRoute()
const { slug } = route.params

// Récupération des données
const { data: article } = await useFetch(`/api/articles/${slug}`)
</script>

Data Fetching

useFetch (recommandé)

<script setup>
// Auto-dédupliqué, cache, SSR-friendly
const { data, pending, error, refresh } = await useFetch('/api/users')
</script>

<template>
  <div v-if="pending">Chargement...</div>
  <div v-else-if="error">Erreur : {{ error.message }}</div>
  <ul v-else>
    <li v-for="user in data" :key="user.id">{{ user.nom }}</li>
  </ul>
</template>

Options de useFetch

<script setup>
const { data } = await useFetch('/api/products', {
  // Clé de cache unique
  key: 'products-list',

  // Paramètres de requête
  query: { category: 'electronics', limit: 10 },

  // Méthode et body
  method: 'POST',
  body: { search: 'laptop' },

  // Transformer la réponse
  transform: (response) => response.items,

  // Exécuter uniquement côté client
  server: false,

  // Lazy loading (ne bloque pas la navigation)
  lazy: true
})
</script>

useAsyncData (contrôle total)

<script setup>
const { data: articles } = await useAsyncData(
  'articles-recents',
  () => $fetch('/api/articles', { query: { limit: 5 } }),
  {
    watch: [() => route.query.page] // Re-fetch si la page change
  }
)
</script>

API Routes

Créez des endpoints API dans le dossier server/ :

// server/api/users.get.ts
export default defineEventHandler(async (event) => {
  return [
    { id: 1, nom: 'Alice' },
    { id: 2, nom: 'Bob' }
  ]
})

// server/api/users.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)

  // Validation
  if (!body.nom || !body.email) {
    throw createError({
      statusCode: 400,
      message: 'Nom et email requis'
    })
  }

  // Création (simulée)
  return { id: 3, ...body }
})

// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')
  // Récupération depuis la base de données...
  return { id, nom: 'Utilisateur ' + id }
})

Composants auto-importés

Tous les composants dans components/ sont auto-importés :

components/
├── Button.vue          → <Button />
├── Header/
│   ├── Logo.vue        → <HeaderLogo />
│   └── Nav.vue         → <HeaderNav />
└── UI/
    ├── Card.vue        → <UICard />
    └── Modal.vue       → <UIModal />

Composables

// composables/useAuth.ts
export const useAuth = () => {
  const user = useState('user', () => null)
  const isLoggedIn = computed(() => !!user.value)

  async function login(credentials: { email: string, password: string }) {
    const data = await $fetch('/api/auth/login', {
      method: 'POST',
      body: credentials
    })
    user.value = data.user
  }

  function logout() {
    user.value = null
    navigateTo('/login')
  }

  return { user, isLoggedIn, login, logout }
}

// Utilisation (auto-importé)
<script setup>
const { user, isLoggedIn, logout } = useAuth()
</script>

Layouts

<!-- layouts/default.vue -->
<template>
  <div class="layout">
    <Header />
    <main>
      <slot />
    </main>
    <Footer />
  </div>
</template>

<!-- layouts/admin.vue -->
<template>
  <div class="admin-layout">
    <Sidebar />
    <div class="content">
      <slot />
    </div>
  </div>
</template>
<!-- pages/dashboard.vue -->
<template>
  <div>Dashboard admin</div>
</template>

<script setup>
definePageMeta({
  layout: 'admin'
})
</script>

Middleware

Middleware de page

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isLoggedIn } = useAuth()

  if (!isLoggedIn.value && to.path !== '/login') {
    return navigateTo('/login')
  }
})

// Utilisation
<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>

Middleware global

// middleware/analytics.global.ts
export default defineNuxtRouteMiddleware((to) => {
  // Tracking automatique sur chaque navigation
  console.log('Navigation vers:', to.path)
})

SEO et Meta

<script setup>
// Meta basique
useHead({
  title: 'Mon titre de page',
  meta: [
    { name: 'description', content: 'Description de la page' }
  ]
})

// SEO avancé
useSeoMeta({
  title: 'Article - Mon Blog',
  ogTitle: 'Article - Mon Blog',
  description: 'Description pour le SEO',
  ogDescription: 'Description pour les réseaux sociaux',
  ogImage: '/images/article-cover.jpg',
  twitterCard: 'summary_large_image'
})
</script>

Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  // Modules
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    '@nuxt/image'
  ],

  // Variables d'environnement publiques
  runtimeConfig: {
    // Privé (serveur uniquement)
    apiSecret: process.env.API_SECRET,

    // Public (client et serveur)
    public: {
      apiBase: process.env.API_BASE || 'https://api.exemple.fr'
    }
  },

  // SSR ou Static
  ssr: true, // ou false pour SPA

  // Preset de déploiement
  nitro: {
    preset: 'vercel' // ou 'netlify', 'cloudflare', etc.
  }
})

Déploiement

Génération statique

npm run generate
# Résultat dans .output/public/

Build serveur

npm run build
npm run preview # Test local
# Déployer .output/

Conclusion

Nuxt 3 offre tout ce qu'il faut pour créer des applications Vue.js modernes et performantes. Son approche convention over configuration, les auto-imports et l'intégration fullstack en font un choix excellent pour tout projet, du blog personnel à l'application d'entreprise.