Project layout
<html> and <head> are not part of a component: Dusk writes the document shell.
app/layout.tsx -> src/app.tsx and src/routes/_layout.tsx
app/page.tsx -> src/routes/index.tsx
app/posts/[id]/page.tsx -> src/routes/posts/[id].tsx
app/api/x/route.ts -> src/routes/api/x.ts
middleware.ts -> src/entry-server.ts (the `middleware` option)
next.config.js -> vite.config.ts (dusk options)
public/ -> public/ (same)
.env.local -> .env.local (see Environment variables)
<html> and <head> are not part of a component: Dusk writes the document shell. document.lang sets
<html lang>, route head exports write the tags, and document.head adds trusted markup to every page. The root
component src/app.tsx wraps the routed page.
{
"scripts": {
"dev": "dusk dev",
"build": "dusk build",
"start": "dusk start"
}
}
next dev becomes dusk dev, next build becomes dusk build, and next start becomes dusk start. The dusk command
runs Vite underneath, so the file watching and hot reload are Vite's, as in any Vite app. .env files load the same way
(see Environment variables). dusk start serves the build with the Node adapter, which the
config below enables; The dusk command lists the flags. The files of the app used on
this page:
import { node } from "@shadow-js/dusk/node";
import { dusk } from "@shadow-js/dusk/vite";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [dusk({ adapter: node() })] });
import type { JSX } from "@shadow-js/web";
export default function App(props: { children?: JSX.Element }): JSX.Element {
return <div class="app">{props.children}</div>;
}
src/schema.ts is a stand-in for Zod, Valibot or ArkType: server functions take any
Standard Schema.
import type { StandardSchemaV1 } from "@shadow-js/dusk/server";
/** Wraps a parse function that throws on bad input as a Standard Schema. */
export function schema<T>(parse: (value: unknown) => T): StandardSchemaV1<T, T> {
return {
"~standard": {
version: 1,
vendor: "app",
validate(value) {
try {
return { value: parse(value) };
} catch (error) {
return { issues: [{ message: error instanceof Error ? error.message : "invalid" }] };
}
},
},
};
}