Migration from v7 to v8
Upgrading from Dinou v7 to v8 is completely transparent and requires zero application code changes. All route signatures, React Server Components (RSC), and Server Functions work out of the box.
1. Migration Guide: Zero-Config & Transparent Upgrade
For standard projects consuming Dinou as a dependency, migrating to v8 simply consists of updating the package version. Your application routes, data fetching contracts, component trees, and Server Functions require no modification whatsoever.
1.1 Standard Applications (dependencies: { "dinou": "^8.0.0" })
To upgrade an existing Dinou v7 project, update the dependency in your package.json:
# Using npm
npm install dinou@latest
# Using Bun
bun add dinou@latest
# Using pnpm
pnpm add dinou@latestOptional Best Practice: Declare "type": "module"
Because Dinou v8 is fully compiled as pure ECMAScript Modules (ESM), it is recommended to ensure your project's package.json declares "type": "module":
{
"name": "my-dinou-app",
"type": "module",
"dependencies": {
"dinou": "^8.0.0",
"react": "^19.3.0",
"react-dom": "^19.3.0"
}
}With "type": "module" active, you can optionally rename dinou.config.mjs to dinou.config.js or dinou.config.ts. Both are fully supported:
// dinou.config.js (or dinou.config.ts)
import { defineConfig } from "dinou/config"; // or import { defineConfig } from "dinou";
export default defineConfig({
// Your Dinou configuration
});Full Backwards Compatibility:
- Routes & Conventions:
page.tsx,layout.tsx,page_functions.ts, andlayout_functions.tsremain 100% unchanged. - React 19 Server Components: RSC streaming, Suspense boundaries, and Client Components (
"use client") remain fully compatible. - Server Functions: Action mutations (
"use server") work identically without changes. - Routing & Cache APIs:
useRouter,usePathname,useSearchParams, and<DinouCacheSlot>are 100% preserved.
1.2 Ejected Projects (dinou eject)
If you have previously ejected the Dinou framework into your local codebase (having a local ./dinou/ folder), review the following scenarios:
You do not need to do anything. Ejected Dinou projects are completely standalone and autonomous. You do not need to transform your local files into ESM or touch your project.
Do NOT re-eject (re-ejecting with npx dinou eject would overwrite your custom modifications!). Instead, apply the surgical bug fix directly to your local ./dinou/core/handler.js file as detailed in Section 2 below.
Commit or back up your local ./dinou/ directory, update your dependency to "dinou": "^8.0.0", run npx dinou eject, and re-apply your custom tweaks on the clean v8 ESM foundation.
2. Critical Handler Fix: allowISG: false in Layouts
2.1 Context of the Bug in v7.3.0
In Dinou, layouts can define static optimization policies via layout_functions.ts / layout_functions.js:
revalidate = false: Complete static pre-rendering (SSG).allowISG = false: Prevents dynamic on-demand generation for paths that were not authorized bygetStaticPaths.
In v7.3.0, the request handler (dinou/core/handler.js) executed an overly aggressive check:
// ❌ Buggy condition in v7.3.0:
if (!isDevelopment && lConfig.allowISG === false) {
return new Response("Not Found", { status: 404 });
}This caused any request to a route descending from a layout with allowISG: false to return an immediate 404, even when the route was explicitly authorized in getStaticPaths and its pre-rendered layout.rsc payload was already present in .dinou/dist2/.
2.2 The Fix in handler.js (Exact Code Replacement)
If you maintain an ejected project, open ./dinou/core/handler.js. Locate the if (isLayoutReq) block (around line 1383) and replace the legacy 404 check with the comprehensive verification logic below:
if (isLayoutReq) {
if (layouts && Array.isArray(layouts)) {
for (const [lPath, lParams] of layouts) {
const lConfig = await resolveLayoutFunctionsConfig(lPath, lParams);
if (!isDevelopment && lConfig.allowISG === false) {
return new Response("Not Found", { status: 404 });
}
if (lConfig.validateParams) {
const isValid = await lConfig.validateParams(lParams);
if (!isValid) {
isPathBlocked = true;
break;
}
}
}
} else {
if (!layoutConfig) {
layoutConfig = await resolveLayoutFunctionsConfig(layoutPath, layoutParams);
}
if (!isDevelopment && layoutConfig.allowISG === false) {
return new Response("Not Found", { status: 404 });
}
if (layoutConfig.validateParams) {
const isValid = await layoutConfig.validateParams(layoutParams);
if (!isValid) {
isPathBlocked = true;
}
}
}
if (isPathBlocked) {
return new Response("Not Found", { status: 404 });
}
} else if (!pageConfig) {
pageConfig = await resolvePageFunctionsConfig(
pagePath,
reqSegments,
queryObj,
dynamicParams,
reqPath,
);
isPathBlocked = pageConfig.isPathBlocked;
}if (isLayoutReq) {
if (layouts && Array.isArray(layouts)) {
for (const [lPath, lParams] of layouts) {
const lConfig = await resolveLayoutFunctionsConfig(lPath, lParams);
if (!isDevelopment && lConfig.allowISG === false) {
let isPathAllowed = false;
if (lConfig.staticPathsSet) {
const sortedEntries = Object.entries(lParams || {})
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([k, v]) => {
if (Array.isArray(v)) return [k, v.join(",")];
return [k, String(v)];
});
const serializedQuery = JSON.stringify(sortedEntries);
const singleVal = Object.values(lParams || {})[0];
const singleValStr = Array.isArray(singleVal) ? singleVal.join(",") : String(singleVal);
isPathAllowed = lConfig.staticPathsSet.has(serializedQuery) || lConfig.staticPathsSet.has(singleValStr);
}
if (!isPathAllowed) {
const cleanReq = reqPath.replace(/^\//, "").replace(/\/$/, "");
const rscPath = path.join(process.cwd(), ".dinou/dist2", cleanReq, "layout.rsc");
if (!existsSync(rscPath)) {
isPathBlocked = true;
break;
}
}
}
if (lConfig.validateParams) {
const isValid = await lConfig.validateParams(lParams);
if (!isValid) {
isPathBlocked = true;
break;
}
}
}
} else {
if (!layoutConfig) {
layoutConfig = await resolveLayoutFunctionsConfig(layoutPath, layoutParams);
}
if (!isDevelopment && layoutConfig.allowISG === false) {
let isPathAllowed = false;
if (layoutConfig.staticPathsSet) {
const sortedEntries = Object.entries(layoutParams || {})
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([k, v]) => {
if (Array.isArray(v)) return [k, v.join(",")];
return [k, String(v)];
});
const serializedQuery = JSON.stringify(sortedEntries);
const singleVal = Object.values(layoutParams || {})[0];
const singleValStr = Array.isArray(singleVal) ? singleVal.join(",") : String(singleVal);
isPathAllowed = layoutConfig.staticPathsSet.has(serializedQuery) || layoutConfig.staticPathsSet.has(singleValStr);
}
if (!isPathAllowed) {
const cleanReq = reqPath.replace(/^\//, "").replace(/\/$/, "");
const rscPath = path.join(process.cwd(), ".dinou/dist2", cleanReq, "layout.rsc");
if (!existsSync(rscPath)) {
isPathBlocked = true;
}
}
}
if (layoutConfig.validateParams) {
const isValid = await layoutConfig.validateParams(layoutParams);
if (!isValid) {
isPathBlocked = true;
}
}
}
if (isPathBlocked) {
return new Response("Not Found", { status: 404 });
}
} else if (!pageConfig) {
pageConfig = await resolvePageFunctionsConfig(
pagePath,
reqSegments,
queryObj,
dynamicParams,
reqPath,
);
isPathBlocked = pageConfig.isPathBlocked;
}By checking staticPathsSet and verifying whether layout.rsc exists on disk, valid routes render smoothly through the entire SSR → Loading → Streaming → Client Hydration pipeline. Client Components invoking Server Functions inside static layouts resolve with 100% reliability.
The following sections detail the internal refactorings implemented in the Dinou framework core for v8. They do not require any action from users and are documented here for architectural curiosity and technical reference.
3. Under the Hood: Pure ESM Architecture
Dinou v8 adopts pure ECMAScript Modules ("type": "module") natively across the entire framework repository, eliminating CommonJS artifacts and legacy bridge scripts.
3.1 Source Extensions Unification (.mjs → .js)
All source files within the framework have been unified from .mjs to standard .js:
| Dinou v7 (Legacy Path) | Dinou v8 (Standard ESM) | Purpose |
|---|---|---|
| dinou/node/build.mjs | dinou/node/build.js | Production bundler for Node.js orchestrator |
| dinou/cloudflare/build.mjs | dinou/cloudflare/build.js | Production bundler for Cloudflare Workers |
| dinou/deno/build.mjs | dinou/deno/build.js | Production bundler for Deno Deploy |
| dinou/bun/build.mjs | dinou/bun/build.js | Production bundler for Bun runtime |
| dinou/esbuild/build.mjs | dinou/esbuild/build.js | Client compiler for esbuild |
| dinou/core/export-static.mjs | dinou/core/export-static.js | Static SSG export pipeline |
| dinou/adapters/node.mjs | dinou/adapters/node.js | Node.js HTTP server adapter |
| dinou/index.mjs | dinou/index.js | Unified framework entry point |
| dinou/server.mjs | dinou/server.js | Server utilities and on-demand revalidation |
Deliberate Exception: The compiled output file at .dinou/node/server.mjs deliberately retains .mjs so that Node and Bun execute it in ESM mode irrespective of the consumer project's setup.
3.2 Modernized Exports Map
All subpath exports resolve directly to ESM .js files without conditional CommonJS splits:
"exports": {
".": {
"types": "./dinou/index.d.ts",
"import": "./dinou/index.js",
"default": "./dinou/index.js"
},
"./env": {
"types": "./dinou/env.d.ts"
},
"./server": {
"types": "./dinou/server.d.ts",
"import": "./dinou/server.js",
"default": "./dinou/server.js"
},
"./config": {
"types": "./dinou/index.d.ts",
"import": "./dinou/core/config.js",
"default": "./dinou/core/config.js"
},
"./adapters/node": "./dinou/adapters/node.js",
"./adapters/bun": "./dinou/adapters/bun.js",
"./adapters/deno": "./dinou/adapters/deno.js",
"./adapters/cloudflare": "./dinou/adapters/cloudflare.js",
"./adapters/netlify": "./dinou/adapters/netlify.js"
}3.3 ESM CLI & Modular Ejector
cli.js utilizes static ESM imports (import { program } from "commander"). eject.js encapsulates its logic into an explicit export (export function eject()) called dynamically by the CLI when invoked.
3.4 Canonical Config Resolution
The legacy bridge file dinou/config.js has been removed. The sole canonical configuration source is dinou/core/config.js. Both import { defineConfig } from "dinou/config" and import { defineConfig } from "dinou" remain fully supported and typed.
4. Under the Hood: Universal Edge Renderer Singleton
In Node.js development mode, Dinou runs a 0-Fork Dual-Bundle in-memory engine where both the RSC and SSR HTML engines run inside the same Node process.
Under pure ESM, dynamic resolution of react-server-dom-webpack/server.edge could create subtle context discrepancies during hot-reload recompilations. Dinou v8 solves this by importing the native edge renderer once at startup and caching it as a shared global singleton:
import * as edgeRenderer from "react-server-dom-webpack/server.edge";
globalThis.__DINOU_EDGE_RENDERER__ = edgeRenderer;The internal server bundle accesses the singleton instantly with zero latency, ensuring guaranteed single-context execution across Windows and Linux.
5. Production Health & Verification Checklist
- Standard Upgrade: Run
npm install dinou@latest(zero code changes needed). - Ejected Layout Fix: If ejected and maintaining custom core changes, verify
handler.jshas thestaticPathsSet&layout.rsccheck. - Optional ESM: Add
"type": "module"to yourpackage.jsonand usedinou.config.jsor.ts. - Local Validation: Run
npm run build:node && npm run start:nodeto verify compilation and production server startup.