ENbext.dev
DocumentationConfiguration

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

Sur cette page

Moteur de rendu V8

bext utilise V8 (via rusty_v8 ) pour tous les rendus côté serveur. Le pool de rendu est indépendant du moteur au niveau de l'API publique - historiquement, il prenait également en charge WebKit/JavaScriptCore, mais ce backend a été supprimé depuis les instantanés de tas de la V8 et TurboFan JIT offrait de meilleures performances de démarrage à froid et d'état stable pour chaque déploiement réel.

Bâtiment#

bash
cargo build -p bext-server --release \
  --no-default-features \
  --features v8,tls,nginx-compat,php,route-css

La fonctionnalité v8 fait partie de l'ensemble de fonctionnalités par défaut. Le --no-default-features ci-dessus dépose simplement les bagages dont nous n'avons pas besoin ; voir Construire des drapeaux pour la liste complète.

Configuration#

toml
[render]
workers = 4                  # Dedicated V8 worker threads (default: 4)
bundle_path = "dist/ssr-bundle.js"

Il n'y a pas de champ engine — V8 est la seule option. Les configurations existantes qui définissent engine = "v8" se chargent toujours proprement (le champ inconnu est ignoré).

Sous bext run, les travailleurs de rendu forment un pool multiplexé par défaut ( BEXT_V8_POOL=1 ) : les requêtes sont démultiplexées par identifiant entre les travailleurs, de sorte qu'un travailleur ne reste jamais inactif lors de l'aller-retour IPC entre les rendus, et le corps de la réponse revient sur la mémoire partagée ( BEXT_V8_SHM_IPC=1 ). Un travailleur sature déjà le pipeline de requêtes pour les routes liées aux E/S/au maître ; augmentez workers (ou BEXT_V8_POOL_SIZE ) pour les SSR gourmands en CPU, où le pool évolue de manière presque linéaire. Voir Variables d'environnement pour les indicateurs individuels.

Pipeline de rendu à trois couches#

Le rendu à la demande V8 utilise une architecture en couches pour des performances maximales :

code
Layer 1: React Base (747 KB, built once per process)
  → React + ReactDOM + jsx-runtime as globalThis globals
  → Disk-cached at .bext/v8-react-base.js (survives restarts)

Layer 2: Site Shell (31 KB for simple sites, cached per-site)
  → Root layout + Nav + Footer + shared UI components
  → Disk-cached at .bext/ssr-cache/__shell.js
  → Rebuilt when layout files change

Layer 3: Page Delta (27-51 KB per route, cached per-route)
  → Just the page component (React externalized)
  → Disk-cached at .bext/ssr-cache/{route}.js
  → Rebuilt when page source changes

Performance#

Métrique Sans superposition Avec superposition
Taille du paquet de routage ~800 Ko 27-51 Ko (96 % plus petit)
Temps de compilation de Bun 30 ms 9-14 ms
Évaluation V8 (premier contexte) 100 ms 32ms
Évaluation V8 (ultérieure) 100 ms 7ms
Appel du cache de réponse 6 ms 6ms

Hiérarchie du cache#

Les bundles sont mis en cache à trois niveaux :

  1. Mémoire — DashMap, recherche instantanée, effacée lors du changement de source
  2. Disque — .bext/ssr-cache/, survit aux redémarrages, validé par le fichier mtime
  3. Compile — Sous-processus Bun, s'exécute uniquement en cas d'échec du cache complet

Après un redémarrage avec un cache disque chaud, les routes se chargent sans aucune compilation Bun.

Configuration#

Le pipeline à la demande est configurable via bext.config.toml :

toml
[nextjs]
on_demand = true
cache_ttl = 60          # Response cache TTL in seconds (default: 60)
cache_swr = 300         # Stale-while-revalidate window in seconds (default: 300)
max_shell_size = 524288 # Max shell bundle size in bytes (default: 512KB)

[render]
streaming = true              # Use renderToReadableStream + bext event loop
# streaming_timeout_ms = 5000 # Wall-clock cap for one render
# streaming_stall_ms = 250    # Bail out if no chunk for this long
# rsc = true                  # Expose /__bext/rsc/<path> Flight endpoint

Les sites avec des shells plus grands que max_shell_size (par exemple, les applications full-stack avec Prisma/tRPC) reviennent automatiquement à la compilation complète.

Conseil

Si vous n'avez besoin que de pages entièrement synchrones, configurez streaming = false pour ignorer complètement le pilote de boucle d'événements et obtenir une latence légèrement inférieure. Les composants du serveur asynchrone et use(promise) cesseront de fonctionner, mais les pages de rendu pur ne seront pas affectées.

RSS en streaming#

Lorsque streaming = true, bext pilote le renderToReadableStream de React 19 à partir de react-dom/server directement à l'intérieur de l'isolat V8. La V8 n'a pas de boucle d'événements intégrée, donc bext en implémente une dans Rust :

  • collect_async_render_result pompe perform_microtask_checkpoint() jusqu'à ce que React rapporte done
  • La progression est lue via l'accès brut aux objets V8, donc chaque tick ne coûte presque rien
  • Un flux bloqué (pas de nouveau morceau pour streaming_stall_ms ) renfloue prématurément
  • Un plafond d'horloge murale ( streaming_timeout_ms, 5 000 ms par défaut) délimite le pire des cas

Le mode streaming se déverrouille :

  • Composants du serveur async function Page()
  • Limites <Suspense> avec enfants asynchrones
  • Le crochet use(promise)
  • await fetch(...) et autres travaux asynchrones réservés au serveur

Lorsque streaming = false, bext supprime globalThis.__ReactDOMServer.renderToReadableStream du contexte de la page afin que le wrapper de bundle passe au renderToString synchrone de React. Ce chemin ne peut pas attendre, mais il ignore complètement la boucle d'événements, ce qui est utile pour les pages entièrement synchronisées.

Composants du serveur React (expérimental)#

Définir [render] rsc = true et bext expose /__bext/rsc/<path> pour tout itinéraire correspondant. Le point de terminaison renvoie une véritable charge utile de vol React via react-server-dom-parcel/server.edge.

Le pipeline RSC utilise un bundle distinct et un contexte de page V8 distinct du pipeline HTML SSR :

  1. Le travailleur bun warm compile une entrée RSC par route avec --conditions=react-server afin que React soit résolu en sa version serveur uniquement.
  2. Le travailleur regroupe ce React en ligne sur serveur uniquement (en ignorant la règle externe habituelle react). L'externaliser laisserait dans la sortie des références import.meta.require("react") que le mode de script classique de V8 ne peut pas analyser, et forcerait le moteur d'exécution à utiliser le React normal du bundle SSR, qui est incompatible avec les chemins de code réservés au serveur de react-server-dom-parcel.
  3. Le bundle est évalué dans un nouveau contexte V8, mis en cache séparément du contexte SSR (la clé de cache du contexte de page a l'espace de noms page:rsc:... vs page:ssr:... )
  4. renderToReadableStream de react-server-dom-parcel/server.edge produit la charge utile Flight, que collect_async_rsc_result draine via le même pilote de boucle d'événements que le streaming SSR.

La charge utile de vol d'une petite route atterrit en ~ 10 ms à chaud, ~ 50 ms à froid (compilation + évaluation combinées). Le point de terminaison est actuellement uniquement localhost et additif : il ne remplace pas encore la réponse HTML SSR.

En-têtes de réponse#

Les pages rendues en V8 incluent :

En-tête Valeur
x-bext-mode on-demand
x-bext-cache hit, stale ou miss
cache-control public, max-age={ttl}, stale-while-revalidate={swr}
etag Hachage FNV du corps de réponse
x-content-type-options nosniff
x-frame-options SAMEORIGIN
referrer-policy strict-origin-when-cross-origin
link Conseils de préchargement extraits de la page HTML
content-encoding gzip (lorsque le client accepte)
vary Accept-Encoding

ETag permet les réponses 304 Not Modified — aucun transfert lors de visites répétées.

Architecture#

Fil d'évaluation dédié#

L'évaluation V8 s'exécute sur un seul thread de système d'exploitation dédié avec une pile de 32 Mo :

  1. Isolat unique à longue durée de vie — jamais abandonné (évite rusty_v8 v130 SEGV)
  2. Nouveau contexte par requête — chaque rendu obtient une portée propre
  3. Répartition basée sur les canaux — les appelants envoient leur travail via mpsc::channel
  4. Délai d'expiration de 30 secondes — empêche les rendus suspendus
  5. GC périodique — low_memory_notification() tous les 50 rendus

Temps mis en cache#

Le répertoire source mtime est mis en cache avec une durée de vie d'une seconde pour éviter les déplacements du système de fichiers à chaque requête (plus de 53 fichiers pour un site typique).

Fonctions du pont#

La V8 expose les fonctions natives du pont IO au code groupé :

Fonction But
__readFile(path) Lire des fichiers à partir du disque
__readDir(path) Lister le contenu du répertoire
__httpFetch(url, opts) Requêtes HTTP via ureq
__dbQuery(db, sql, params) Requêtes SQLite
__env(key) Accès aux variables d'environnement
__log(level, msg) Journalisation structurée

Polyremplissages#

Les isolats nus V8 n'ont pas d'API Node.js /Bun. bext fournit des polyfills pour :

  • process.env, process.versions.node, process.cwd()
  • console.log/warn/error/info
  • TextEncoder / TextDecoder
  • setTimeout / clearTimeout
  • queueMicrotask
  • window, document, navigator, location (tronçons SSR minimaux)

Dépannage#

<head> vide dans le rendu HTML#

Le renderToString de React 19 peut supprimer les enfants <head> dans la V8. bext contourne ce problème en extrayant le contenu principal du côté serveur layout.tsx et en l'injectant dans le code HTML si <head></head> est vide.

Coquille trop grande#

Si vous voyez « Shell trop grand, utilisant le bundle complet » dans les journaux, la chaîne de mise en page du site comprend de lourdes dépendances (Prisma, tRPC, etc.). Le shell dépasse max_shell_size et revient à une compilation complète. Ceci est attendu pour les applications full-stack.

Pour augmenter le seuil :

toml
[nextjs]
max_shell_size = 1048576  # 1MB

Utilisation de la mémoire#

Surveillez la mémoire V8 et les performances de rendu :

bash
bext-server diagnose --pass <admin-password>
bext-server diagnose --pass <admin-password> --format json

Vérification du timing d'évaluation#

Répartition de la synchronisation des journaux V8 par requête :

code
V8 eval timing: polyfills_ms=0, base_shell_ms=1, bundle_ms=0, render_ms=5, total_ms=7
  • base_shell_ms : Temps d'évaluation de la base React + shell du site (1 ms après le premier contexte, 21 ms la première fois)
  • bundle_ms : délai d'évaluation du delta de page (0 ms pour les lots de 27 à 51 Ko)
  • render_ms : Temps de rendu React vers chaîne (5-10 ms)

En rapport#

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

Saisissez un sujet, une commande ou une question.