Next.js : du statique dynamique

8 minutes de lecture

Next.js : du statique dynamique

Next.js est un framework React open source qui a pour particularité d’être fullstack gérant aussi bien la partie serveur que client. Cela en fait un framework tout-en-un avec une expérience développeur très poussée. Les dernières releases 9.3 et 9.4 ont apporté leur lot de nouvelles fonctionnalités, l’occasion pour nous de vous parler de ce framework que nous apprécions particulièrement.

Avant de commencer, deux commandes à taper dans votre terminal :

npx create-next-app myapp
cd myapp && yarn dev

Nous sommes prêts !

Le tooling

Au niveau expérience développeur (DX), Next.js prône le « zéro configuration » et apporte les mêmes fonctionnalités (et plus) que Create React App, c’est à dire :

  • Abstraction des bundler/compiler (avec support des assets, css, fonts…) ;
  • Support de TypeScript ;
  • Fast Refresh (~Hot Reload - pas encore disponible dans CRA) ;
  • Gestion des variables d’environnement ;
  • Code splitting ;
  • Build de production ;

Next.js apporte même le support des imports absolus (ou via des alias), terminé donc la soupe aux points et place à des imports plus concis :

-import Button from '../../../../components/Button'
+import Button from 'components/Button'

Il vous suffira d’ajouter l’option baseUrl à votre fichier jsconfig.json (où tsconfig.json) :

{
  "compilerOptions": {
    "baseUrl": "."  }
}

Voici donc pour les fonctionnalités plus orientées tooling, passons maintenant aux fonctionnalités inhérentes à Next.js qui en font un framework à part et disons-le précurseur.

File-System Routing

Next.js apporte son propre système de routing (plus besoin donc de react-router / reach-router) ayant pour particularité d’être basé sur le file-system. Le framework va donc mapper les fichiers placés dans le dossier pages/ et créer vos routes :

pages/
├── index.tsx # http://localhost/
├── dashboard.tsx # http://localhost/dashboard
│
├────── posts/
│       ├── index.tsx # http://localhost/posts
│       └── [slug].tsx # http://localhost/posts/post-slug-1# http://localhost/posts/post-slug-2# http://localhost/posts/post-slug-3
│
├────── api/ # (server side)
│       ├── posts/
│       │   ├── [slug].ts # http://localhost/api/posts/1
│       │                 # http://localhost/api/posts/2
│       │                 # http://localhost/api/posts/3
│       │
│       └── dashboard.ts # http://localhost/api/dashboard

Le code des fichiers placés dans le dossier api/ sera uniquement exécuté côté serveur, c’est donc ici que vous créez les endpoints de votre API.

Voici un exemple de code pour le fichier dashboard.tsx qui sera exécuté côté client :

import React from 'react';

const Dashboard = () => (
  <section>
    <h1>Dashboard</h1>
  </section>
);

export default Dashboard;

Dans l’exemple ci-dessus, vous avez peut-être remarqué le fichier api/posts/[slug].ts, la syntaxe [slug].ts indique un wildcard que nous pouvons récupérer dans notre code :

// Code côté serveur, non exposé au front

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { slug } = req.query;
  // fetch data for this slug
  // return json data
};

Plus besoin d’écrire votre routeur : votre arborescence le décrit ! En gérant aussi bien les routes clients que serveurs, il est très rapide de développer des applications fullstack. Lancez simplement yarn dev et bénéficiez directement du back et du front (le tout avec le confort du Fast Refresh).

Un des principaux intérêt de Next.js réside dans sa capacité à proposer une stratégie « static first » très simplement, grâce à des mécanismes de data fetching uniques.

Data fetching avec Next.js

Depuis les pages front (pages/dashboard.tsx par exemple), Next.js propose plusieurs mécanismes afin de récupérer des données.

La manière classique serait de lancer une requête fetch à l’initialisation de votre composant :

import React, { useState, useEffect } from 'react';
import Widget from 'components/Widget';

const Dashboard = () => {
  const [data, setData] = useState();

  const loadData = async () => {
    const response = await axios.get('/api/dashboard');
    setData(response.data);
  };

  useEffect(() => {
    loadData();
  }, []);

  return (
    <section>
      <h1>Dashboard</h1>
      {data && <Widget data={data} />}
    </section>
  );
};

export default Dashboard;

Cependant Next.js est un framework fullstack, pourquoi ne pas plutôt récupérer les données côté serveur et ainsi éviter d’avoir à gérer le cas des chargements ?

Next.js propose un moyen de récupérer les données et de générer votre composant côté serveur (c’est-à-dire faire du SSR, Server Side Rendering).

Activer le rendu serveur

Afin de bénéficier du SSR avec Next.js, il vous suffit juste d’ajouter une fonction getServerSideProps dans votre page :

import React, { useState, useEffect } from 'react';
import Widget from 'components/Widget';

export const getServerSideProps = async () => {  const response = await axios.get('/api/dashboard');  // Vous pouvez aussi vous connecter directement à une base de données  // car le code est exécuté uniquement côté serveur #yolo  return {    props: {      dashboardData: response.data,    },  };};
const Dashboard = ({ dashboardData }) => (
  <section>
    <h1>Dashboard</h1>
    <Widget data={dashboardData} />
  </section>
);

export default Dashboard;

Votre page est alors générée automatiquement côté serveur ! Mais Next.js ne s’arrête pas là, car il propose de servir une version statique de votre page, c’est-à-dire de servir une page HTML/CSS directement.

Cette page est générée au moment du build et non lorsque l’utilisateur se rend sur cette page depuis son navigateur.

Activer la génération statique

De la même façon que le SSR, il suffit d’utiliser la méthode getStaticProps :

import React, { useState, useEffect } from 'react';
import Widget from 'components/Widget';

export const getStaticProps = async () => {  const response = await axios.get('/api/dashboard');

  return {
    props: {
      dashboardData: response.data,
    },
  };
};

const Dashboard = ({ dashboardData }) => (
  <section>
    <h1>Dashboard</h1>
    <Widget data={dashboardData} />
  </section>
);

export default Dashboard;

La page sera alors générée lors du yarn build ! Cependant cela peut poser problème, car il nous faudra re-builder notre app pour mettre à jour les données de notre dashboard. Depuis la version 9.4, Next.js propose un mécanisme (encore en beta) pour rafraîchir automatiquement ces données, ainsi il vous suffit de renseigner l’option unstable_revalidate avec une valeur en seconde :

export const getStaticProps = async () => {
  const response = await axios.get('/api/dashboard');

  return {
    props: {
      dashboardData: response.data,
    },
    unstable_revalidate: 3600, // 1 heure  };
};

De cette manière, si un visiteur se rend sur votre dashboard et que celle-ci a été générée il y a plus d’une heure, Next.js re-buildera une version avec des données fraîches ! Ainsi la plupart des utilisateurs recevront une version statique, alors que quelques-uns auront « une version dynamique » (avec un maximum d’une génération par heure).

Génération statique avec des routes dynamiques

Comme nous l’avons vu plus haut, certaines de nos routes peuvent-être dynamiques et avoir par exemple un slug :

pages/
├────── posts/
│       └── [slug].tsx # http://localhost/posts/post-slug-1

Comment générer des pages statiques si nous ne savons pas par avance quels seront les slugs ?

Pour répondre à ce cas, Next.js met à disposition la fonction getStaticPaths (en combinaison de getStaticProps) permettant d’indiquer avec quels slugs nous voulons générer des versions statiques des pages :

const getStaticPaths = async () => {
  return {
    paths: [
      { params: { slug: 'post-slug-1' } },
      { params: { slug: 'post-slug-2' } },
      { params: { slug: 'post-slug-3' } },
    ],
    fallback: true,
  };
};

Ainsi avec cette configuration, nous générerons 3 versions statiques lors du build :

Un utilisateur se rendant sur la page http://localhost/posts/post-slug-4, déclenchera un rendu dynamique côté serveur car l’option fallback est à true. En passant cette option à false, l’utilisateur se verra servir une page 404 !

Pour finir, voici deux démos illustrant parfaitement la génération statique :

On s’y met ?

Next.js est donc un framework à part, qui en plus de fournir une expérience développeur top-notch apporte des fonctionnalités uniques et novatrices. L’alliage du statique / dynamique permet de développer des applications très performantes et d’éviter de nombreuses problématiques liées à la récupération de données.

Next.js développé par Vercel (anciennement Zeit) propose aussi une plateforme d’hébergement taillée sur-mesure pour ce framework. La version gratuite de Vercel est souvent suffisante, la version Pro permet de débloquer des fonctionnalités avancées (multi membres, builds concurrents, protection par mot de passe…).

Côté backend, Next.js reste assez brut côté fonctionnalités se rapprochant d’un serveur express. Le combo gagnant serait de coupler Next.js avec le framework Nest.js (pas évident ces noms…) apportant ainsi la maturité de l’écosystème back (ORM, injection de dépendances, validations, migrations…). Le package nest-next permet de faciliter l’intégration de Next.js. Nous en reparlerons dans un prochain article…

Si vous n’avez pas encore essayé Next.js, nous vous conseillons donc de vous y mettre ! Chez Premier Octet, nous sommes fans 🖤

La doc c’est par ici !

Continuer la discussion sur Twitter