ENbext.dev
DocumentationIntégrations de frameworks

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Rendu à la demande Next.js

Pointez bext vers un répertoire Next.js app/ et commencez à servir – pas de répertoire next build, pas de répertoire .next/. Les pages se compilent à la première demande et sont mises en cache. Comme PHP, mais avec React.

Démarrage rapide#

bash
# In your Next.js project directory:
bext dev .
toml
# bext.config.toml
[server]
app_dir = "."

[framework]
type = "nextjs"

[nextjs]
on_demand = true

C'est ça. Visitez http://localhost:3000/ et bext :

  1. Scanner app/ pour les fichiers page.tsx + layout.tsx
  2. Compilez la page demandée à la demande (travailleur au pain chaud, ~15 ms)
  3. Rendu via V8 — renderToString pour les pages de synchronisation, renderToReadableStream + pilote de boucle d'événements personnalisé de bext pour le streaming
  4. Mettre en cache à la fois le bundle compilé et le contexte de page V8 chaud (le prochain appel à la même route ignore entièrement l'évaluation du bundle, ~ 5 ms)
  5. Servir les requêtes suivantes à partir du cache

Comment ça marche#

code
GET /blog/hello-world
    │
    ▼
Route scanner matches: app/blog/[slug]/page.tsx
    │
    ▼
ISR cache check → miss (first request)
    │
    ▼
Bundle cache check → miss (never compiled)
    │
    ▼
Generate wrapper entry:
  import Layout from "app/layout.tsx"
  import Page from "app/blog/[slug]/page.tsx"
  renderToReadableStream(<Layout><Page params={{slug:"hello-world"}} /></Layout>)
    │
    ▼
Warm bun worker → compiled JS (~15ms)
    │
    ▼
V8 streaming render → HTML (~3ms warm, ~30ms cold)
    │
    ▼
Cache: bundle (by route + mtime), warm V8 context (by bundle hash), HTML (ISR with TTL)
    │
    ▼
Return HTML

Next request: GET /blog/hello-world → ISR cache hit → 0ms

Configuration#

toml
[nextjs]
compat = "full"               # Real layout.tsx, metadata, full Next.js semantics
on_demand = true              # Enable on-demand compilation
bundler = "bun"               # bun (warm worker pool) or turbopack (in-process Rust)
bundle_cache_max = 500        # Max cached compiled bundles

[render]
streaming = true              # React 19 renderToReadableStream + bext event loop
# rsc = true                  # Experimental RSC Flight payload endpoint
# streaming_timeout_ms = 5000 # Wall-clock cap for a single render
# streaming_stall_ms = 250    # Bail out if no chunk for this long

[nextjs.aliases]              # Import path aliases
"@/*" = "src/*"

compat = "full" signifie que bext comprend le vrai layout.tsx, les exportations de métadonnées, les composants du serveur asynchrone et le reste du contrat App Router. Les alias de chemin proviennent automatiquement de votre tsconfig.json.

Ce qui est pris en charge#

Fonctionnalité Statut Remarques
Pages statiques ( page.tsx ) ✅ Compilé + mis en cache à la première demande
Mises en page ( layout.tsx ) ✅ Composition imbriquée (racine → feuille), cache shell par chaîne de mise en page
Itinéraires dynamiques ( [slug] ) ✅ Paramètres extraits de l'URL
Fourre-tout ( [...path] ) ✅ Tous les segments restants capturés
Groupes de routages ( (group) ) ✅ Aucun impact sur l'URL, organisation uniquement
generateStaticParams ✅ Utilisé pour les conseils de pré-rendu
Métadonnées statiques ( export const metadata ) ✅ Détecté au moment de l'analyse
Itinéraires API ( route.ts ) ✅ GET/POST/etc., paramètres dynamiques via le deuxième argument
Composants du serveur asynchrone ✅ Natif — streaming = true utilise la boucle d'événements V8 de renderToReadableStream + bext
Limites <Suspense> ✅ Le streaming SSR pousse les enfants asynchrones jusqu'à leur achèvement
Crochet use(promise) ✅ Piloté par la boucle de rendu en streaming
redirect() / notFound() ✅ Traduit en HTTP 307/404 via la capture Error.prototype.digest
next/headers / next/cookies ✅ Lire la demande en direct
Composants 'use client' ✅ Rendu par le serveur au format HTML, le bundle client les hydrate
Charge utile du vol RSC (expérimental) ⚙️ [render] rsc = true expose /__bext/rsc/<path> renvoyant une charge utile de vol réelle via react-server-dom-parcel
Actions du serveur ( 'use server' ) 🔜 Intégration via decodeReply/decodeAction de react-server-dom-parcel — en cours

Adaptateurs des API Next.js#

L’adaptateur require de V8 dans bext prend directement en charge les API serveur courantes de Next.js, sans installation :

tsx
import Link from "next/link"                          // → <a>
import Image from "next/image"                        // → <img>
import { redirect, notFound } from "next/navigation"  // → 307 / 404 via digest
import { headers, cookies } from "next/headers"       // → live request
import { revalidateTag } from "next/cache"            // → bext cache invalidation
import { NextResponse } from "next/server"            // → response builder

redirect() et notFound() définissent leur champ digest, puis bext installe sur l’enveloppe SSR un accesseur d’écriture Error.prototype.digest qui capture l’URL ou le code et convertit l’erreur levée en réponse HTTP appropriée.

Composants serveur asynchrones, Suspense et use()#

Les composants serveur asynchrones, <Suspense> avec des enfants asynchrones et le hook use(promise) fonctionnent tous nativement lorsque [render] streaming = true, la valeur par défaut des nouveaux projets :

tsx
// app/blog/[slug]/page.tsx
export default async function Page({ params }) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  return <article>{post.title}</article>;
}

V8 n’a pas de boucle d’événements native ; bext en implémente donc une en Rust : collect_async_render_result fait progresser perform_microtask_checkpoint() jusqu’à ce que le renderToReadableStream de React indique done, avec détection des blocages (streaming_stall_ms) et limite de durée totale (streaming_timeout_ms). L’accès direct aux objets V8 évite le coût de compilation et d’évaluation JavaScript à chaque tour. Un rendu en streaming à chaud utilise ainsi environ 1 ms du thread d’évaluation.

Si vous définissez streaming = false, bext utilise le renderToString synchrone de React. Ce chemin ne prend pas en charge les composants serveur asynchrones, les limites Suspense ou use(promise), mais évite entièrement la boucle d’événements : utile pour les projets sans récupération asynchrone de données côté serveur.

Composants du serveur React (expérimental)#

Définissez [render] rsc = true pour que bext expose /__bext/rsc/<path> sur toute route correspondante. Ce point de terminaison renvoie une véritable charge Flight React produite par react-server-dom-parcel/server.edge et prête à être consommée par createFromFetch côté client.

En interne, bext compile un bundle RSC distinct pour chaque route via le processus bun maintenu à chaud :

  • Bun est appelé avec --conditions=react-server ; React est donc résolu vers sa version réservée au serveur
  • Le bundle intègre ce React réservé au serveur au lieu de le laisser externe : cela permet d’exécuter le bundle Flight comme un script classique autonome dans V8 et évite que ses mécanismes internes React entrent en conflit avec le React habituel du bundle HTML SSR
  • Le bundle compilé est évalué dans un contexte de page V8 distinct de celui du bundle HTML SSR. Le contexte SSR et le contexte RSC sont mis en cache séparément : la requête suivante sur la même route réutilise les deux
  • Une charge utile de vol pour un petit itinéraire atterrit en ~ 10 ms à chaud, ~ 50 ms à froid (compilation + évaluation combinées)

Le point de terminaison Flight est actuellement uniquement localhost et additif : il ne remplace pas encore la réponse HTML SSR. Le manifeste client, le bootstrap d’hydratation et le câblage 'use server' sont les éléments restants avant que RSC ne soit entièrement de bout en bout.

Cache de paquet#

Les bundles compilés sont mis en cache par route :

Clé route:/blog/:slug
Annulation Modifications de page.tsx mtime, ou toute modification de layout.tsx mtime
Nombre maximum d'entrées 500 (configurable via bundle_cache_max)
Expulsion LRU lorsqu'il est plein

Lorsque vous modifiez un fichier :

  • L'observateur de fichiers détecte le changement de mtime
  • Entrée du cache du bundle invalidée pour les routes concernées
  • Entrées du cache ISR effacées pour ces routes
  • La prochaine requête est recompilée à la demande (~ 15 ms)

Performance#

Validé de bout en bout via le harnais SSR dans harnesses/ssr-dev/ (33 tests de base + 10 tests spécifiques au streaming par rapport à une véritable application de luminaire Next.js) :

Scénario Temps
Compilation de petits pains chauds (page unique + chaîne de mise en page) ~13 à 15 ms
Rendu à froid V8 (évaluation du bundle + premier appel) ~30 ms
Rendu chaud V8 (contexte de page mis en cache) ~3 à 5 ms
Rendu en streaming – composant serveur asynchrone + Suspense ~5 ms
Charge utile du vol RSC (froid) ~50 ms
Charge utile du vol RSC (chaud) ~10 ms
Accès au cache ISR sous-milliseconde
Changement de fichier → invalider → recompiler ~15 ms

Voir bext.dev/benchmarks pour les chiffres complets et comment ils ont été mesurés.

Exemple : nextjs-blog#

Le répertoire examples/nextjs-blog contient une application Next.js 15 complète :

code
examples/nextjs-blog/
  bext.config.toml
  package.json
  app/
    layout.tsx           # Root layout with nav + footer
    page.tsx             # Home page with post list
    blog/
      [slug]/
        page.tsx         # Dynamic blog post with async params
    api/
      revalidate/
        route.ts         # ISR revalidation endpoint

Exécutez-le :

bash
cd examples/nextjs-blog
bun install
bext dev .

Visitez http://localhost:3060/ – la page d’accueil s’affiche instantanément. Cliquez sur un article de blog : compilé lors de la première visite, mis en cache ensuite.

Comparaison avec next build#

next build + next start bext à la demande
Étape de construction Obligatoire (~ 30-60 s) Aucun
Chargement de la première page Pré-rendu ~ 20 ms de compilation
Chargements ultérieurs Instantané Instantané (cache ISR)
Changement de fichier Reconstruction requise Recompilation automatique (~ 15 ms)
Mémoire Répertoire.next/ complet Cache par itinéraire
Démarrage à froid Charger tous les itinéraires Ne chargez rien, compilez à la demande

Le bext à la demande est idéal pour les sites de développement et de contenu où le retour instantané compte plus que la première peinture de zéro ms.

Attention

streaming = false désactive les composants du serveur asynchrone, les limites Suspense et use(promise). Définissez ceci uniquement si vos pages n'ont pas de récupérations asynchrones côté serveur et que vous souhaitez ignorer complètement la boucle d'événements V8.

En rapport#

Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.