Agado Dev

Prérendu d'une Page dans une Single Page Application

Illustration de prerendering dans une SPA

Construire une application mono-page (SPA) avec une architecture frontend et backend séparée offre de nombreux avantages : c’est modulaire, évolutif et cela simplifie le développement. Cependant, cette architecture introduit également un défi commun—comment fournir une page statique optimisée pour le partage sur les réseaux sociaux et le référencement par les moteurs de recherche. Le rendu côté client (CSR), typique des SPA, peut entraîner des temps de rendu plus lents et compliquer l’indexation des pages par les robots.

Dans cet article, nous allons explorer comment relever ce défi en utilisant une approche simple pour prérendre une SPA basée sur React sans recourir à un méta-framework.


Le contexte

Pour mon cas d’utilisation, je construis une application SaaS simple avec la pile technologique suivante:

  • Frontend: React et Vite

  • Backend: Fastify avec une base de données PostgreSQL

  • Hosting: Déployé sur Fly.io

Cette stack est idéale pour mes besoins:

  • Performance: Suffisamment rapide pour le champ d’application du produit

  • Facilité de développement: Rapide à builder et à déployer

  • Economique: Hébergé sur une instance partagée avec 1 CPU et peu de mémoire (256 Mo pour le frontend, 512 Mo pour le backend)

Cependant, l’approche CSR pour la page d’atterrissage présentait deux problèmes majeurs:

  • Rendu initial lent: Le rendu du contenu côté client retarde le temps d’affichage significatif

  • Problèmes de SEO: Les robots des moteurs de recherche ont souvent du mal à indexer les pages rendues en JavaScript

Avec @tanstack/react-router, une solution potentielle serait de mettre en œuvre le rendu côté serveur (SSR) via le méta-framework https://tanstack.com/start. Cependant, pour un cas d’utilisation simple comme celui-ci, avons-nous vraiment besoin de la complexité du SSR? La réponse est: Non.


Pourquoi Prérendre au Lieu du SSR?

React prend en charge le rendu côté serveur depuis la version 0.4.0 (publiée en 2013). Cela signifie que nous pouvons tirer parti de ses capacités de rendu pour générer statiquement du HTML lors de la phase de construction sans introduire un méta-framework. Voyons comment mettre cela en œuvre

Étape 1 : Point d'Entrée Serveur

Créez un point d'entrée dédié pour rendre l'application sous forme de chaîne HTML sur le serveur en utilisant un historique en mémoire pour le routage. Voici à quoi cela ressemble:

main.server.tsx

import { QueryClient } from "@tanstack/react-query";
import { createMemoryHistory } from "@tanstack/react-router";
import { StrictMode } from "react";
import { renderToString } from "react-dom/server";
import { App } from "./App";
import { createRouter } from "./router";
export async function render(url: string) {
const queryClient = new QueryClient();
const router = createRouter({ queryClient });
const memoryHistory = createMemoryHistory({
initialEntries: ["/"],
});
router.update({
context: { queryClient },
history: memoryHistory,
});
await router.load();
const html = renderToString(
<StrictMode>
<App queryClient={queryClient} router={router} />
</StrictMode>
);
return { html };
}

Étape 2 : Préparer le Modèle HTML

Modifiez votre index.html pour inclure des placeholders <!--app-head--> and <!--app-html--> Ces espaces réservés seront remplacés lors du prérendu

index.html

<!DOCTYPE html>
<html>
<head>
<!-- Keep your head tags here -->
<!--app-head-->
</head>
<body>
<div id="root"><!--app-html--></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

Étape 3 : Double Compilation

Vous aurez besoin de deux compilations:

  • Build SPA: L’application classique côté client

  • Build SSR: Un bundle côté serveur pour le prérendu

Exécutez les commandes suivantes pour les compilations respectives:

vite build
vite build --ssr ./src/main.server.tsx --outDir dist/server

Étape 4 : Script de prérendering

Créez un script pour générer statiquement le HTML de votre page d’accueil. Ce script:

  • Render une version rendue côté serveur de l’application

  • Injecte le HTML généré dans le template.

  • Enregistre le fichier résultant sous forme de index.html

scripts/prerender.tsx

import dotenv from "dotenv";
import fs from "fs";
import path, { dirname } from "node:path";
import { fileURLToPath } from "node:url";
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export async function prerenderHomePage() {
const { render } = await import("../dist/server/main.server.js");
const htmlTemplate = fs.readFileSync(
path.resolve(__dirname, "..", "dist", "index.html"),
{
encoding: "utf-8",
}
);
const rendered = await render();
const html = htmlTemplate.replace(`<!--app-html-->`, rendered.html ?? "");
const outputPath = path.resolve(__dirname, "..", "dist/index.html");
fs.writeFileSync(outputPath, html);
console.log(`Prerendered HTML saved to: ${outputPath}`);
}
prerenderHomePage();

Étape 5 : Tout Combiner

Ajoutez les scripts nécessaires à votre package.json pour un processus de construction fluide:

package.json

{
// ...
"scripts": {
"dev": "vite dev",
"build": "pnpm build:app && pnpm prerender",
"build:app": "vite build",
"build:server": "vite build --ssr ./src/main.server.tsx --outDir dist/server",
"prerender": "pnpm build:server && tsx ./scripts/prerender.tsx"
},
// ...
}

Exécutez pnpm build pour générer à la fois la SPA et la page d’accueil prérendue. Le résultat ? Une SPA traditionnelle avec une page d’accueil React prérendue statiquement, optimisée pour la vitesse et le SEO!


Conclusion

Le prérendu offre une solution légère pour améliorer les performances et le SEO de votre SPA sans la complexité d’un méta-framework. En exploitant les capacités de rendu côté serveur de React et un pipeline de construction simple, vous pouvez fournir une page d’accueil rapide et indexable tout en maintenant votre pile technologique minimale et efficace. Essayez et partagez vos résultats !