Introduction/Guides/SPAs

Comment créer des applications monopages avec Next.js

Next.js prend entièrement en charge la création d'applications monopages (Single-Page Applications ou SPAs).

Cela inclut des transitions de route rapides avec préchargement, la récupération de données côté client, l'utilisation d'API navigateur, l'intégration avec des bibliothèques client tierces, la création de routes statiques et bien plus encore.

Si vous avez déjà une SPA, vous pouvez migrer vers Next.js sans modifier significativement votre code. Next.js vous permet ensuite d'ajouter progressivement des fonctionnalités serveur selon vos besoins.

Qu'est-ce qu'une application monopage ?

La définition d'une SPA varie. Nous définirons une "SPA stricte" comme :

  • Rendu côté client (CSR) : L'application est servie par un seul fichier HTML (par exemple index.html). Chaque route, transition de page et récupération de données est gérée par JavaScript dans le navigateur.
  • Pas de rechargement complet de page : Plutôt que de demander un nouveau document pour chaque route, le JavaScript côté client manipule le DOM de la page actuelle et récupère les données selon les besoins.

Les SPAs strictes nécessitent souvent de grandes quantités de JavaScript à charger avant que la page ne soit interactive. De plus, les cascades de données côté client peuvent être difficiles à gérer. La création de SPAs avec Next.js peut résoudre ces problèmes.

Pourquoi utiliser Next.js pour les SPAs ?

Next.js peut diviser automatiquement vos bundles JavaScript et générer plusieurs points d'entrée HTML pour différentes routes. Cela évite de charger du code JavaScript inutile côté client, réduisant ainsi la taille du bundle et permettant des chargements de page plus rapides.

Le composant next/link précharge automatiquement les routes, vous offrant les transitions rapides d'une SPA stricte, mais avec l'avantage de persister l'état de routage de l'application dans l'URL pour le partage.

Next.js peut commencer comme un site statique ou même une SPA stricte où tout est rendu côté client. Si votre projet évolue, Next.js vous permet d'ajouter progressivement plus de fonctionnalités serveur (par exemple les composants serveur React, les actions serveur, etc.) selon vos besoins.

Exemples

Explorons les modèles courants utilisés pour créer des SPAs et comment Next.js les résout.

Utilisation du hook use de React dans un fournisseur de contexte

Nous recommandons de récupérer les données dans un composant parent (ou une mise en page), de retourner la Promise, puis de déballer la valeur dans un composant client avec le hook use de React.

Next.js peut commencer la récupération de données tôt sur le serveur. Dans cet exemple, c'est la mise en page racine - le point d'entrée de votre application. Le serveur peut immédiatement commencer à diffuser une réponse au client.

En "élevant" votre récupération de données vers la mise en page racine, Next.js démarre les requêtes spécifiées sur le serveur avant tout autre composant de votre application. Cela élimine les cascades côté client et évite d'avoir plusieurs allers-retours entre le client et le serveur. Cela peut également améliorer considérablement les performances, car votre serveur est plus proche (et idéalement colocalisé) de votre base de données.

Par exemple, mettez à jour votre mise en page racine pour appeler la Promise, mais ne l'attendez pas.

import { UserProvider } from './user-provider'
import { getUser } from './user' // une fonction côté serveur

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  let userPromise = getUser() // NE PAS utiliser await

  return (
    <html lang="en">
      <body>
        <UserProvider userPromise={userPromise}>{children}</UserProvider>
      </body>
    </html>
  )
}

Bien que vous puissiez différer et passer une seule Promise comme prop à un composant client, nous voyons généralement ce modèle associé à un fournisseur de contexte React. Cela permet un accès plus facile depuis les composants clients avec un hook React personnalisé.

Vous pouvez transmettre une Promise au fournisseur de contexte React :

'use client';

import { createContext, useContext, ReactNode } from 'react';

type User = any;
type UserContextType = {
  userPromise: Promise<User | null>;
};

const UserContext = createContext<UserContextType | null>(null);

export function useUser(): UserContextType {
  let context = useContext(UserContext);
  if (context === null) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
}

export function UserProvider({
  children,
  userPromise
}: {
  children: ReactNode;
  userPromise: Promise<User | null>;
}) {
  return (
    <UserContext.Provider value={{ userPromise }}>
      {children}
    </UserContext.Provider>
  );
}

Enfin, vous pouvez appeler le hook personnalisé useUser() dans n'importe quel composant client et déballer la Promise :

'use client'

import { use } from 'react'
import { useUser } from './user-provider'

export function Profile() {
  const { userPromise } = useUser()
  const user = use(userPromise)

  return '...'
}

Le composant qui consomme la Promise (par exemple Profile ci-dessus) sera suspendu. Cela permet une hydratation partielle. Vous pouvez voir le HTML streamé et prérendu avant que JavaScript n'ait fini de charger.

SPAs avec SWR

SWR est une bibliothèque React populaire pour la récupération de données.

Avec SWR 2.3.0 (et React 19+), vous pouvez adopter progressivement des fonctionnalités serveur aux côtés de votre code existant de récupération de données côté client basé sur SWR. Il s'agit d'une abstraction du modèle use() ci-dessus. Cela signifie que vous pouvez déplacer la récupération de données entre le client et le serveur, ou utiliser les deux :

  • Uniquement côté client : useSWR(key, fetcher)
  • Uniquement côté serveur : useSWR(key) + données fournies par RSC
  • Mixte : useSWR(key, fetcher) + données fournies par RSC

Par exemple, enveloppez votre application avec <SWRConfig> et un fallback :

import { SWRConfig } from 'swr'
import { getUser } from './user' // une fonction côté serveur

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <SWRConfig
      value={{
        fallback: {
          // Nous n'utilisons PAS await getUser() ici
          // Seuls les composants qui lisent ces données seront suspendus
          '/api/user': getUser(),
        },
      }}
    >
      {children}
    </SWRConfig>
  )
}

Comme il s'agit d'un composant serveur, getUser() peut lire en toute sécurité les cookies, les en-têtes ou communiquer avec votre base de données. Aucune route API séparée n'est nécessaire. Les composants clients sous <SWRConfig> peuvent appeler useSWR() avec la même clé pour récupérer les données utilisateur. Le code du composant avec useSWR ne nécessite aucune modification par rapport à votre solution existante de récupération côté client.

'use client'

import useSWR from 'swr'

export function Profile() {
  const fetcher = (url) => fetch(url).then((res) => res.json())
  // Le même modèle SWR que vous connaissez déjà
  const { data, error } = useSWR('/api/user', fetcher)

  return '...'
}

Les données fallback peuvent être prérendues et incluses dans la réponse HTML initiale, puis immédiatement lues dans les composants enfants utilisant useSWR. Le sondage, la revalidation et la mise en cache de SWR s'exécutent toujours uniquement côté client, préservant ainsi toute l'interactivité dont vous dépendez pour une SPA.

Comme les données initiales fallback sont automatiquement gérées par Next.js, vous pouvez maintenant supprimer toute logique conditionnelle précédemment nécessaire pour vérifier si data était undefined. Lorsque les données sont en cours de chargement, la limite <Suspense> la plus proche sera suspendue.

SWRRSCRSC + SWR
Données SSRCross IconCheck IconCheck Icon
Streaming pendant SSRCross IconCheck IconCheck Icon
Déduplication des requêtesCheck IconCheck IconCheck Icon
Fonctionnalités côté clientCheck IconCross IconCheck Icon

SPAs avec React Query

Vous pouvez utiliser React Query avec Next.js à la fois côté client et serveur. Cela vous permet de créer à la fois des SPAs strictes, ainsi que de tirer parti des fonctionnalités serveur de Next.js couplées à React Query.

Apprenez-en plus dans la documentation de React Query.

Rendu des composants uniquement dans le navigateur

Les composants clients sont prérendus pendant next build. Si vous souhaitez désactiver le prérendu pour un composant client et le charger uniquement dans l'environnement du navigateur, vous pouvez utiliser next/dynamic :

import dynamic from 'next/dynamic'

const ClientOnlyComponent = dynamic(() => import('./component'), {
  ssr: false,
})

Cela peut être utile pour les bibliothèques tierces qui dépendent d'API navigateur comme window ou document. Vous pouvez également ajouter un useEffect qui vérifie l'existence de ces API, et s'ils n'existent pas, retourner null ou un état de chargement qui serait prérendu.

Routage superficiel côté client

Si vous migrez depuis une SPA stricte comme Create React App ou Vite, vous pourriez avoir du code existant qui utilise le routage superficiel pour mettre à jour l'état de l'URL. Cela peut être utile pour les transitions manuelles entre les vues de votre application sans utiliser le routage par système de fichiers par défaut de Next.js.

Next.js vous permet d'utiliser les méthodes natives window.history.pushState et window.history.replaceState pour mettre à jour la pile d'historique du navigateur sans recharger la page.

Les appels pushState et replaceState s'intègrent au routeur Next.js, vous permettant de vous synchroniser avec usePathname et useSearchParams.

'use client'

import { useSearchParams } from 'next/navigation'

export default function SortProducts() {
  const searchParams = useSearchParams()

  function updateSorting(sortOrder: string) {
    const urlSearchParams = new URLSearchParams(searchParams.toString())
    urlSearchParams.set('sort', sortOrder)
    window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
  }

  return (
    <>
      <button onClick={() => updateSorting('asc')}>Trier par ordre croissant</button>
      <button onClick={() => updateSorting('desc')}>Trier par ordre décroissant</button>
    </>
  )
}
'use client'

import { useSearchParams } from 'next/navigation'

export default function SortProducts() {
  const searchParams = useSearchParams()

  function updateSorting(sortOrder) {
    const urlSearchParams = new URLSearchParams(searchParams.toString())
    urlSearchParams.set('sort', sortOrder)
    window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
  }

  return (
    <>
      <button onClick={() => updateSorting('asc')}>Trier par ordre croissant</button>
      <button onClick={() => updateSorting('desc')}>Trier par ordre décroissant</button>
    </>
  )
}

Apprenez-en plus sur le fonctionnement du routage et de la navigation dans Next.js.

Utilisation d'actions serveur dans les composants clients

Vous pouvez adopter progressivement les actions serveur tout en utilisant des composants clients. Cela vous permet de supprimer le code passe-partout pour appeler une route API, et d'utiliser plutôt des fonctionnalités React comme useActionState pour gérer les états de chargement et d'erreur.

Par exemple, créez votre première action serveur :

'use server'

export async function create() {}

Vous pouvez importer et utiliser une action serveur depuis le client, de manière similaire à l'appel d'une fonction JavaScript. Vous n'avez pas besoin de créer manuellement un point de terminaison API :

'use client'

import { create } from './actions'

export function Button() {
  return <button onClick={() => create()}>Créer</button>
}

Apprenez-en plus sur la modification de données avec les actions serveur.

Export statique (optionnel)

Next.js prend également en charge la génération d'un site entièrement statique. Cela présente certains avantages par rapport aux SPAs strictes :

  • Division de code automatique : Au lieu d'envoyer un seul index.html, Next.js générera un fichier HTML par route, afin que vos visiteurs obtiennent le contenu plus rapidement sans attendre le bundle JavaScript client.
  • Expérience utilisateur améliorée : Au lieu d'un squelette minimal pour toutes les routes, vous obtenez des pages entièrement rendues pour chaque route. Lorsque les utilisateurs naviguent côté client, les transitions restent instantanées et similaires à une SPA.

Pour activer un export statique, mettez à jour votre configuration :

next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'export',
}

export default nextConfig

Après avoir exécuté next build, Next.js créera un dossier out avec les ressources HTML/CSS/JS pour votre application.

Remarque : Les fonctionnalités serveur de Next.js ne sont pas prises en charge avec les exports statiques. En savoir plus.

Migration de projets existants vers Next.js

Vous pouvez migrer progressivement vers Next.js en suivant nos guides :

Si vous utilisez déjà une SPA avec le routeur Pages, vous pouvez apprendre comment adopter progressivement le routeur App.