Skip to main content

Migrating from Next.js

ness migrate next # in the Next.js project
ness migrate next --dry-run # print the plan without touching a file

The command requires a clean git working tree, so the migration can be reviewed as a diff. It writes MIGRATION.md listing every file it moved, every import it rewrote, and everything left for you.

Only the App Router is migrated. Convert a Pages Router project to the App Router in Next first.

What carries over unchanged​

The route conventions are nearly the same, which is what makes this mechanical rather than a rewrite:

Next.jsNess.js
app/page.tsxapp/routes/page.tsx
app/layout.tsx (nested)app/routes/.../layout.tsx
app/layout.tsx (root)app/root.tsx
app/loading.tsxapp/routes/.../loading.tsx
app/template.tsxapp/routes/.../template.tsx
app/sitemap.ts, app/robots.ts, app/manifest.tssame names under app/routes/
app/error.tsxapp/routes/.../error.tsx
app/not-found.tsxapp/routes/.../not-found.tsx
app/forbidden.tsx, app/unauthorized.tsxsame names under app/routes/
app/global-error.tsxapp/routes/global-error.tsx
app/api/x/route.tsapp/routes/api/x/route.ts
@slot/…, default.tsxidentical (see Next.js parity)
(.)x, (..)x, (...)x intercepting routesidentical (overlay semantics)
icon.png, apple-icon.png, opengraph-image.*identical
[id], [...rest], [[...rest]], (group), _privateidentical

Route Handlers keep their shape too: a route module exporting GET, POST, and so on is dispatched by method in both frameworks.

Imports rewritten automatically​

Next.jsNess.jsNote
next/linkreact-routerhref becomes to
next/image@nessframework/core
next/script@nessframework/core
next/dynamic@nessframework/coresame {loading, ssr} options
next/navigation hooks@nessframework/coresame names
next/cache@nessframework/cacheunstable_cache becomes cached
next/og@nessframework/core/og
next/font/local@nessframework/core/font

What needs a human​

These are reported, not guessed at. A codemod that silently reshapes code it did not fully understand is worse than one that tells you what it skipped.

Server Components. A Next async page component becomes a loader in an adjacent page.server file plus a synchronous component reading useLoaderData(). The split depends on what the component awaits.

// Next
export default async function Page() {
const products = await db.product.findMany();
return <List products={products} />;
}
app/routes/page.server.ts
export async function loader() {
return db.product.findMany();
}
app/routes/page.tsx
export default function Page() {
return <List products={useLoaderData()} />;
}

Server Actions. A "use server" function becomes an action export in page.server, submitted through <Form>.

redirect() and notFound(). In Ness these are thrown from a loader or action, from @nessframework/core/server/responses.

cookies() and headers(). Next reads these from an implicit request. Read them from the request argument your loader or action already receives.

NextRequest / NextResponse. These are the Web-standard Request and Response globals in Ness; drop the import.

generateStaticParams. List the paths under router.prerender in ness.config.mjs.

metadata and generateMetadata. Both carry over as route exports β€” export const metadata = {…} and export async function generateMetadata({params, loaderData}) render the same tags, title templates included. In classic mode generateMetadata also runs on client navigations, so derive from loaderData rather than reaching into a database. The element components (Title, Description, …) remain available. See Next.js parity.

next.config.js. redirects, rewrites, headers, and images move into the server section of ness.config.mjs; the option shapes match.

Middleware. middleware.ts at the project root runs once per request, like Next's. Ness also has per-segment middleware.ts files, which are usually the closer fit β€” the placement has to be chosen.

unstable_noStore(), connection(), after(). All exist, from @nessframework/core/server: noStore()/connection() take the response out of the shared cache, after() runs once the response has been sent. fetch(url, {next: {revalidate, tags}}) works on the server and stores through the Ness cache adapters.

Route segment config. revalidate, dynamic, runtime, maxDuration, dynamicParams, fetchCache, preferredRegion and experimental_ppr are read off the page source the way Next reads them. basePath and assetPrefix move from next.config.js into the router section of ness.config.mjs.

After migrating​

ness typegen
ness build

Review the diff, work through MIGRATION.md, then delete it.