Aller au contenu principal
Version : 2.4.3

Génération de site statique (SSG)

In architecture, we mentioned that the theme is run in Webpack. Mais attention : cela ne veut pas dire qu'il a toujours accès aux globales du navigateur ! Le thème est construit deux fois :

  • During server-side rendering, the theme is compiled in a sandbox called React DOM Server. You can see this as a "headless browser", where there is no window or document, only React. Le rendu côté serveur produit des pages HTML statiques.
  • During client-side rendering, the theme is compiled to JavaScript that gets eventually executed in the browser, so it has access to browser variables.
SSR or SSG?

Server-side rendering and static site generation can be different concepts, but we use them interchangeably.

À proprement parler, Docusaurus est un générateur de sites statiques, car il n'y a pas de temps d'exécution côté serveur. Nous effectuons un rendu statique vers des fichiers HTML qui sont déployés sur un CDN, au lieu d'effectuer un pré-rendu dynamique à chaque demande. This differs from the working model of Next.js.

Therefore, while you probably know not to access Node globals like process (or can we?) or the 'fs' module, you can't freely access browser globals either.

import React from 'react';

export default function WhereAmI() {
return <span>{window.location.href}</span>;
}

This looks like idiomatic React, but if you run docusaurus build, you will get an error:

ReferenceError: window is not defined

This is because during server-side rendering, the Docusaurus app isn't actually run in browser, and it doesn't know what window is.

What about process.env.NODE_ENV?

One exception to the "no Node globals" rule is process.env.NODE_ENV. En fait, vous pouvez l'utiliser dans React, parce que Webpack injecte cette variable en tant que global :

import React from 'react';

export default function expensiveComp() {
if (process.env.NODE_ENV === 'development') {
return <>This component is not shown in development</>;
}
const res = someExpensiveOperationThatLastsALongTime();
return <>{res}</>;
}

During Webpack build, the process.env.NODE_ENV will be replaced with the value, either 'development' or 'production'. Vous obtiendrez alors des résultats de construction différents après avoir éliminé le code mort :

import React from 'react';

export default function expensiveComp() {
if ('development' === 'development') {
+ return <>This component is not shown in development</>;
}
- const res = someExpensiveOperationThatLastsALongTime();
- return <>{res}</>;
}

Understanding SSR

React n'est pas seulement un moteur d'exécution d'interface utilisateur dynamique, c'est aussi un moteur de templates. Comme les sites de Docusaurus contiennent principalement des contenus statiques, ils devraient pouvoir fonctionner sans aucun JavaScript (avec lequel React fonctionne), mais uniquement en HTML/CSS. Et c'est ce que propose le rendu côté serveur : rendre statiquement votre code React en HTML, sans aucun contenu dynamique. Un fichier HTML n'a aucune notion de l'état du client (il s'agit uniquement de balises), il ne doit donc pas dépendre des API du navigateur.

These HTML files are the first to arrive at the user's browser screen when a URL is visited (see routing). Ensuite, le navigateur récupère et exécute d'autres codes JS pour fournir les parties « dynamiques » de votre site - tout ce qui est implémenté avec JavaScript. Toutefois, avant cela, le contenu principal de votre page est déjà visible, ce qui permet un chargement plus rapide.

Dans les applications rendues côté client uniquement, tous les éléments DOM sont générés côté client avec React, et le fichier HTML ne contient toujours qu'un seul élément racine sur lequel React doit monter le DOM; en SSR, React est déjà confronté à une page HTML entièrement construite, et il n'a besoin que de corréler les éléments DOM avec le DOM virtuel dans son modèle. Cette étape est appelée « hydratation ». Après que React ait hydraté le balisage statique, l'application commence à fonctionner comme n'importe quelle application React normale.

Note that Docusaurus is ultimately a single-page application, so static site generation is only an optimization (progressive enhancement, as it's called), but our functionality does not fully depend on those HTML files. This is contrary to site generators like Jekyll and Docusaurus v1, where all files are statically transformed to markup, and interactiveness is added through external JavaScript linked with <script> tags. If you inspect the build output, you will still see JS assets under build/assets/js, which are, really, the core of Docusaurus.

Escape hatches

Si vous souhaitez afficher sur votre écran un contenu dynamique qui dépend de l'API du navigateur pour être fonctionnel, par exemple :

  • Our live codeblock, which runs in the browser's JS runtime
  • Our themed image that detects the user's color scheme to display different images
  • The JSON viewer of our debug panel which uses the window global for styling

Il se peut que vous ayez besoin de vous échapper du SSR, car le HTML statique ne peut rien afficher d'utile sans connaître l'état du client.

attention

Il est important que le premier rendu côté client produise exactement la même structure DOM que le rendu côté serveur, sinon, React corrélera le DOM virtuel avec les mauvais éléments DOM.

Therefore, the naïve attempt of if (typeof window !== 'undefined) {/* render something */} won't work appropriately as a browser vs. server detection, because the first client render would instantly render different markup from the server-generated one.

You can read more about this pitfall in The Perils of Rehydration.

Nous fournissons plusieurs moyens plus fiables d'échapper au SSR.

<BrowserOnly>

If you need to render some component in browser only (for example, because the component relies on browser specifics to be functional at all), one common approach is to wrap your component with <BrowserOnly> to make sure it's invisible during SSR and only rendered in CSR.

import BrowserOnly from '@docusaurus/BrowserOnly';

function MyComponent(props) {
return (
<BrowserOnly fallback={<div>Loading...</div>}>
{() => {
const LibComponent =
require('some-lib-that-accesses-window').LibComponent;
return <LibComponent {...props} />;
}}
</BrowserOnly>
);
}

It's important to realize that the children of <BrowserOnly> is not a JSX element, but a function that returns an element. C'est une décision de conception. Considérons ce code :

import BrowserOnly from '@docusaurus/BrowserOnly';

function MyComponent() {
return (
<BrowserOnly>
{/* DON'T DO THIS - doesn't actually work */}
<span>page url = {window.location.href}</span>
</BrowserOnly>
);
}

While you may expect that BrowserOnly hides away the children during server-side rendering, it actually can't. When the React renderer tries to render this JSX tree, it does see the {window.location.href} variable as a node of this tree and tries to render it, although it's actually not used! L'utilisation d'une fonction garantit que nous ne laissons le moteur de rendu voir le composant du navigateur que lorsqu'il est nécessaire.

useIsBrowser

You can also use the useIsBrowser() hook to test if the component is currently in a browser environment. It returns false in SSR and true is CSR, after first client render. Utilisez ce hook si vous avez seulement besoin d'effectuer certaines opérations conditionnelles du côté client, mais pas de rendre une interface complètement différente.

import useIsBrowser from '@docusaurus/useIsBrowser';

function MyComponent() {
const isBrowser = useIsBrowser();
const location = isBrowser ? window.location.href : 'fetching location...';
return <span>{location}</span>;
}

useEffect

Lastly, you can put your logic in useEffect() to delay its execution until after first CSR. This is most appropriate if you are only performing side-effects but don't get data from the client state.

function MyComponent() {
useEffect(() => {
// Only logged in the browser console; nothing is logged during server-side rendering
console.log("I'm now in the browser");
}, []);
return <span>Some content...</span>;
}

ExecutionEnvironment

The ExecutionEnvironment namespace contains several values, and canUseDOM is an effective way to detect browser environment.

Beware that it essentially checked typeof window !== 'undefined' under the hood, so you should not use it for rendering-related logic, but only imperative code, like reacting to user input by sending web requests, or dynamically importing libraries, where DOM isn't updated at all.

a-client-module.js
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

if (ExecutionEnvironment.canUseDOM) {
document.title = "Je suis chargé !";
}