DocsCadres Fullstack

De SvelteKit à Nexy

Dans Nexy, un projet SvelteKit conserve ses pages et mises en page Svelte sous forme de fichiers .svelte simples, tandis que +server.ts, +page.server.ts se chargent, les actions de formulaire et les hooks se déplacent vers les fichiers Python à côté d'eux.

Chaque section ci-dessous montre une habitude SvelteKit, puis la même chose dans Nexy.

Choisissez d'abord le mode

Votre application SvelteKit est Mode Nexy Les pages sont
une application avec navigation client mode client, useCSR = True .svelte fichiers sous src/routes/
principalement du contenu, quelques parties interactives mode serveur, la valeur par défaut .nexy pages avec Svelte îles
nexyconfig.py
from nexy.core.models import NexyConfigModelfrom nexy.frontend import svelte  class NexyConfig(NexyConfigModel):    useFF = svelte()    useCSR = True    useAliases = {"@": "src"}

Les sections ci-dessous utilisent le mode client, sauf indication contraire.

+page.svelte

src/routes/about/+page.svelte
<h1>About</h1>

Une page est le fichier lui-même, nommé d'après son URL :

src/routes/about.svelte
<h1>About</h1>

src/routes/+page.svelte est src/routes/index.svelte.

Itinéraires dynamiques et params

src/routes/blog/[slug]/+page.svelte
<script lang="ts">  import { page } from "$app/state";</script> <h1>{page.params.slug}</h1>

Les segments atteignent la page en tant qu'accessoires :

src/routes/blog/[slug].svelte
<script lang="ts">  let { slug }: { slug: string } = $props();</script> <h1>{slug}</h1>

Dans tout autre composant, le magasin params de @nexy/client les contient : $params.slug. Les dossiers [...rest] et (group) fonctionnent comme dans SvelteKit.

+layout.svelte

src/routes/+layout.svelte
<script lang="ts">  let { children } = $props();</script> <header>My app</header><main>{@render children()}</main>

Il n'y a pas de app.html : la racine layout.svelte est propriétaire du document.

src/routes/layout.svelte
<script lang="ts">  import type { Snippet } from "svelte";  let { children }: { children?: Snippet } = $props();</script> <html>  <body>    <header>My app</header>    <main>{@render children?.()}</main>  </body></html>

+server.ts

src/routes/api/items/[id]/+server.ts
import { json } from "@sveltejs/kit"; export function GET({ params }) {  return json({ id: Number(params.id) });}

Le même export GET, dans Python :

src/routes/api/items/[id].py
async def GET(id: int):    return {"id": id}
src/routes/api/items/index.py
from pydantic import BaseModel, Field from nexy.http import JSONResponse  class NewItem(BaseModel):    name: str    price: float = Field(gt=0)  async def POST(item: NewItem):    return JSONResponse(item.model_dump(), status_code=201)

error()

src/routes/api/orders/[id]/+server.ts
import { error, json } from "@sveltejs/kit"; export function GET({ params, url }) {  if (Number(params.id) > 100) error(404, "No such order");  return json({ id: Number(params.id), currency: url.searchParams.get("currency") ?? "EUR" });}
src/routes/api/orders/[id].py
from nexy.http import HTTPException  async def GET(id: int, currency: str = "EUR"):    if id > 100:        raise HTTPException(status_code=404, detail="No such order")    return {"id": id, "currency": currency}

load dans +page.server.ts

src/routes/users/+page.server.ts
export async function load() {  return { users: await db.user.findMany() };}
src/routes/users/+page.svelte
<script lang="ts">  let { data } = $props();</script> <ul>  {#each data.users as user (user.id)}    <li>{user.name}</li>  {/each}</ul>

En mode client, la page demande ses données à un gestionnaire :

src/routes/api/users.py
async def GET():    return [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]
src/routes/users.svelte
<script lang="ts">  let users = $state<{ id: number; name: string }[]>([]);   $effect(() => {    fetch("/api/users").then((response) => response.json()).then((list) => (users = list));  });</script> <ul>  {#each users as user (user.id)}    <li>{user.name}</li>  {/each}</ul>

En mode serveur, l'élément principal d'une page .nexy est la fonction load, et elle s'exécute pendant que le serveur restitue :

src/routes/people.nexy
---users = [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]---<ul>  {% for user in users %}  <li>{{ user.name }}</li>  {% endfor %}</ul>

Actions de formulaire et use:enhance

src/routes/todos/+page.server.ts
export const actions = {  default: async ({ request }) => {    const data = await request.formData();    await db.todo.create({ title: data.get("title") });  },};
src/routes/todos/+page.svelte
<script lang="ts">  import { enhance } from "$app/forms";</script> <form method="POST" use:enhance>  <input name="title" />  <button>Add</button></form>

Une action est une fonction Python avec un client généré et typé :

src/actions/todos.py
from pydantic import BaseModel from nexy.decorators import Action, revalidate  class Todo(BaseModel):    title: str    done: bool = False  @Actionasync def add_todo(title: str) -> Todo:    revalidate("/todos")    return Todo(title=title)
src/routes/todos.svelte
<script lang="ts">  import { useAction } from "@nexy/client";  import { add_todo } from "@nexy/actions";   let title = $state("");  const save = useAction(add_todo, { onSuccess: () => (title = "") });</script> <form onsubmit={(e) => { e.preventDefault(); save.run({ title }); }}>  <input bind:value={title} />  <button disabled={$save.pending}>Add</button></form>

revalidate("/todos") joue le rôle de invalidateAll() : la page se récupère à nouveau.

goto, liens et $app/state

src/lib/Nav.svelte
<script lang="ts">  import { goto } from "$app/navigation";  import { page } from "$app/state";</script> <a href="/about">About</a><span>{page.url.pathname}</span><button onclick={() => goto("/dashboard")}>Dashboard</button>
src/components/Nav.svelte
<script lang="ts">  import Link from "@nexy/Link.svelte";  import { pathname, router } from "@nexy/client";</script> <Link href="/about" activeClass="active">About</Link><span>{$pathname}</span><button onclick={() => router.push("/dashboard")}>Dashboard</button>

hooks.server.ts

src/hooks.server.ts
export async function handle({ event, resolve }) {  if (event.url.pathname.startsWith("/dashboard") && !event.cookies.get("session")) {    return new Response(null, { status: 307, headers: { location: "/login" } });  }  const response = await resolve(event);  response.headers.set("x-app", "demo");  return response;}

Le test de chemin est le dossier. Une racine middleware.py enveloppe tout, une dashboard/middleware.py uniquement les routes en dessous :

src/routes/middleware.py
from nexy.http import Request  async def middleware(request: Request, call_next):    response = await call_next(request)    response.headers["x-app"] = "demo"    return response
src/routes/dashboard/middleware.py
from nexy.http import RedirectResponse, Request  async def middleware(request: Request, call_next):    if "session" not in request.cookies:        return RedirectResponse("/login", status_code=307)    return await call_next(request)
src/routes/dashboard/stats.py
async def GET():    return {"visits": 12}

prerender et entries

src/routes/blog/[slug]/+page.ts
export const prerender = true; export function entries() {  return [{ slug: "hello" }, { slug: "world" }];}

Exportez getStaticParams depuis la page pour un document pré-rendu par valeur :

src/routes/posts/[slug].svelte
<script lang="ts" module>  export function getStaticParams() {    return [{ slug: "hello" }, { slug: "world" }];  }</script> <script lang="ts">  let { slug }: { slug: string } = $props();</script> <h1>{slug}</h1>

En mode serveur, revalidate = 60 dans le frontmatter d'une page .nexy le met en cache par URL :

src/routes/news.nexy
---revalidate = 60---<h1>News</h1>

<svelte:head>

src/routes/blog/+page.svelte
<svelte:head>  <title>Blog</title></svelte:head>

En mode client, écrivez les balises à la racine layout.svelte. En mode serveur, une page .nexy les définit, et sa mise en page les écrit avec {{ useMetadata().html() }} :

src/routes/blog.nexy
---metadata = {"title": "Blog", "description": "Posts about Nexy"}---<h1>Blog</h1>

Svelte composants dans les pages du serveur

src/components/Counter.svelte
<script lang="ts">  let { start = 0 }: { start?: number } = $props();  let count = $state(start);</script> <button onclick={() => count++}>{count}</button>

En mode serveur, le composant est un îlot d'une page .nexy :

src/routes/home.nexy
---from "@/components/Counter.svelte" import Counter---<h1>Static heading</h1><Counter start=3 directive="client:visible" />

+error.svelte

src/routes/notfound.svelte répond aux URL inconnues en mode client, notfound.nexy en mode serveur et les clients API obtiennent {"detail": ...}.

$env/static/private

src/lib/server/db.ts
import { DATABASE_URL } from "$env/static/private";
src/settings.py
import os DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///dev.db")

Courir

SvelteKit Nexy
npx sv create nx new, modèle web-fbr-svelte
vite dev nx dev
vite build avec un adaptateur, puis node build nx build, puis nx start
static/ public/, servi à /
src/lib/ et $lib src/components/ et @

Prochaines étapes

Modifier sur GitHub

Entrée ouvre le résultat, Échap ferme.