Migrate from Next.js
Nowaki keeps the parts of Next.js that make it productive and swaps the runtime model: HTML-first with islands, on a Rust toolchain. Here's how the concepts line up.
Concept mapping
| Next.js | Nowaki |
|---|---|
app/page.tsx / pages/ | routes/index.tsx (file-based) |
app/layout.tsx | routes/_layout.tsx (nests per directory) |
| Middleware | routes/_middleware.ts (nests) |
Server Components / getServerSideProps | route loader (server-only, returns data) |
| Server Actions | route action + "use server" functions |
"use client" components | components under islands/ |
Route handlers (route.ts) | routes/api/*.ts (per-method exports) |
[slug] dynamic routes | [slug] dynamic routes |
| React | Preact, with react → preact/compat aliasing |
The big difference: islands, not "use client"
In Next, a page is a React tree and you carve out server vs client boundaries. In Nowaki, a page is HTML by default and you opt specific components into the browser by putting them in islands/. The mental shift: instead of asking "what can be a server component?", ask "what actually needs to be interactive?". Everything else ships zero JS.
React libraries
Nowaki aliases react, react-dom, and react/jsx-runtimeto their preact/compat equivalents, so many React libraries work unchanged in your islands. Hooks (useState, useEffect, …) come frompreact/hooks or through the compat layer.
Data & mutations
// Next: getServerSideProps → Nowaki: export const loader
export const loader = async ({ params }) => ({ post: await db.post(params.slug) });
// Next: Server Action → Nowaki: "use server" function
// actions/posts.ts
"use server";
export async function like(id) { /* ...runs on the server... */ }What to expect
- Use explicit file extensions in relative imports (
../islands/Counter.tsx). - No app-router streaming/Suspense semantics yet; Nowaki has opt-in
streaming = trueper route. - Nowaki is alpha — APIs can still shift. Start with a small surface and grow.
Goal: a Next.js developer should be able to read a Nowaki app and know where everything goes within minutes. If something doesn't map cleanly, that's a gap worth filing on GitHub.