Créer votre première application
Ce parcours construit une application bext PRISM minimale à partir de zéro : la générer, examiner les deux fichiers de configuration, ajouter un layout racine et une page dont le loader lit la session et appelle le SDK de la plateforme, puis l'exécuter localement avec rechargement à chaud. Quand vous serez prêt à la mettre sur un domaine, poursuivez avec Déployer un site PRISM.
Générer le squelette
Créez une nouvelle application à partir du modèle de démarrage PRISM :
bext new my-app --template @bext/starter-prism
cd my-app
bun install
Le répertoire my-app ne doit pas déjà exister. Cela met en place un petit projet :
bext.config.toml, tsconfig.json, package.json, .gitignore, README.md et une
arborescence de routes src/app/ avec un layout, une page d'accueil et une route
/api/hello. La commande initialise aussi un dépôt git puis affiche les étapes suivantes
(bun install, puis bext dev) ; avec --no-post-install, elle saute l'étape git et
affiche seulement les étapes suivantes. D'autres modèles de démarrage existent
(@bext/starter-blog, -docs, -saas, -api…) : voir la Référence CLI.
Les fichiers de configuration
bext.config.toml
Le modèle de démarrage fournit une configuration minimale. Son cœur (extrait ci-dessous ; le
fichier généré contient aussi [build.css] et [compile]) déclare une application PRISM qui
surveille ses répertoires source pour le rechargement à chaud et effectue le rendu avec l'ISR :
[server]
app_dir = "."
[framework]
type = "prism"
[build]
watch_dirs = ["src/app", "src/components"]
live_reload = true
[rendering]
mode = "isr"
revalidate = 60
Voir la Référence de configuration pour chaque section.
tsconfig.json
PRISM compile le JSX directement en chaînes de caractères, la configuration TypeScript pointe donc le JSX vers le framework (extrait) :
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@bext-stack/framework",
"moduleResolution": "bundler",
"paths": { "@/*": ["./src/*"] }
}
}
Le layout racine
Le layout.tsx racine reçoit la page en tant que children. Il peut posséder le document
<html>, comme ci-dessous, ou transmettre children tel quel et laisser chaque page rendre
son propre document (c'est le choix du modèle de démarrage). Le tsconfig.json ci-dessus
pointe déjà le JSX vers le framework ; vous pouvez aussi l'indiquer fichier par fichier avec
le pragma :
/** @jsxImportSource @bext-stack/framework */
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<title>My App</title>
</head>
<body>{children}</body>
</html>
);
}
Un composant est simplement une fonction qui renvoie du HTML sous forme de chaîne (ou une promesse de chaîne) ; aucun JavaScript n'est envoyé au navigateur tant que vous n'ajoutez pas d'island.
Une page avec un loader
Un page.tsx effectue le rendu de la route. Placez le chargement des données dans un
loader : le composant de page ne voit jamais request, les cookies et les en-têtes se
lisent donc dans le loader. Ici, le loader lit la session et incrémente un compteur dans le
magasin KV via le SDK de la plateforme.
La session vient de createSession (point d'entrée @bext-stack/framework/auth), un cookie
signé que vous déclarez une fois dans src/lib/session.ts : voir
Loaders et actions pour sa création et la connexion.
/** @jsxImportSource @bext-stack/framework */
import { createSdk } from "@bext-stack/framework/sdk";
import { session } from "@/lib/session"; // createSession<{ name: string }>(…)
const sdk = createSdk("my-app");
export async function loader({ request }) {
const user = session.read(request); // null si absente, altérée ou expirée
const visits = Number((await sdk.kv.get("visits")) ?? 0) + 1;
await sdk.kv.set("visits", visits, 3600); // valeur + TTL (secondes)
return { user, visits };
}
export default function Page({ data }) {
return (
<main>
<h1>Hello{data.user ? `, ${data.user.name}` : ""}</h1>
<p>Page views: {data.visits}</p>
</main>
);
}
La valeur de retour du loader arrive sur la page en tant que props.data. Voir
Loaders et actions pour le cycle de vie complet, y compris action
pour les mutations, qui s'exécute avant le loader sur POST/PUT/PATCH/DELETE.
createSdk("my-app") connecte votre application au SDK de la plateforme. Le SDK authentifie
votre application automatiquement et cantonne toutes les données à son app id ; vous ne
transmettez aucun token.
Appeler l'API de la plateforme depuis un loader
Un loader peut aussi appeler l'API de plateforme tRPC. Les requêtes
sont en GET (avec une enveloppe d'entrée superjson quand la procédure prend une entrée), et
le résultat que vous voulez est imbriqué à result.data.json. Les cookies de votre
application ne sont pas envoyés à manage.inklura.fr : authentifiez-vous donc avec un jeton
Bearer rangé dans les secrets de votre application. auth.getSession ne prend
aucune entrée et demande la permission auth:read :
export async function loader() {
const token = await sdk.secrets.get("INKLURA_TOKEN");
const res = await fetch("https://manage.inklura.fr/api/trpc/auth.getSession", {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) return { me: null }; // par exemple 401 si le jeton est invalide ou expiré
const body = await res.json();
return { me: body.result.data.json };
}
Le jeton a une date d'expiration. Un jeton invalide ou expiré vaut absence d'identifiants :
l'appel s'exécute alors comme un visiteur anonyme, ce qui échoue en général en UNAUTHORIZED
(401). Renouvelez le secret quand c'est le cas.
Pour un client autonome complet (@trpc/client typé, pagination et gestion des erreurs),
voir Appeler l'API depuis un script.
Exécutez-la
Démarrez le serveur de développement avec rechargement à chaud :
bext dev
Il écoute sur l'interface locale, sur le port de [server] listen (3061 s'il n'est pas
défini) ; si ce port est occupé, il en choisit un libre à proximité et l'indique. Le fichier
généré contient aussi port = 3000 sous [server], mais le serveur ne lit pas cette clé :
pour servir sur 3000, écrivez listen = "127.0.0.1:3000" ou lancez bext dev --port 3000.
Options utiles : --port N, --listen host:port (par exemple 0.0.0.0:3000 pour l'exposer
sur votre réseau), --open, --no-hmr. Modifiez un fichier sous watch_dirs et le
changement est pris en compte en une ou deux secondes. Pour voir les routes découvertes :
bext routes
Une note sur les paquets npm
Les pages s'exécutent dans un isolate, pas dans Node : les modules natifs comme
node:crypto y sont absents ou seulement partiellement simulés, une bibliothèque qui en dépend
ne fonctionnera donc pas de façon fiable au moment du rendu. La méthode
sûre consiste à vendoriser une build ESM en pur JS sous forme de fichiers .js dans src/
et à l'importer par chemin relatif : voir la Vue d'ensemble du SDK.
Étapes suivantes
| Page | Ce qu'elle couvre |
|---|---|
| Vue d'ensemble du SDK | Le framework et le SDK de la plateforme |
| Loaders et actions | Chargement des données, mutations, accès à la session |
| Déployer un site PRISM | Mettre l'application sur un domaine avec TLS automatique |