Modern Full-Stack React 19 framework with React Server Components, Server Functions, and Streaming SSR.
npm install dinoupush, replace, back, forward, and refresh (soft reload).
component with automatic prefetching and opt-in fresh data fetching for volatile states.
page.{jsx,tsx} files located within the src directory structure.
layout.jsx)
error.jsx)
not_found.jsx)
"use server") & Enhanced Suspense
page_functions.ts)
getProps (Static/Layout Data Injection)
getStaticPaths (Static Generation)
revalidate (ISR)
dynamic (Force SSR)
dinou)
dinou)
dinou)
page_functions.ts)
.env)
@/)
bash
npx create-dinou@latest my-app
cd my-app
npm run dev
`
$3
Alternatively, you can set up a project manually:
1. Install dependencies:
`bash
npm install react react-dom dinou
`
2. Create the structure:
Create a src directory in the root of your project and add an entry page.jsx file:
`jsx
// src/page.jsx
export default function Page() {
return Hello, Dinou!
;
}
`
3. Start the server:
`bash
npx dinou dev
`
Routing
Dinou uses a file-system based router. Files named page.{jsx,tsx,js,ts} inside the src directory automatically become routes.
$3
| Pattern | File Path | URL Example | Params (params) |
| :--------------------- | :------------------------------ | :------------ | :-------------------------- |
| Static | src/page.jsx | / | {} |
| Dynamic | src/blog/[slug]/page.jsx | /blog/hello | { slug: "hello" } |
| Optional Dynamic | src/blog/[[slug]]/page.jsx | /blog | { slug: undefined } |
| Catch-all | src/blog/[...slug]/page.jsx | /blog/a/b/c | { slug: ["a", "b", "c"] } |
| Optional Catch-all | src/blog/[[...slug]]/page.jsx | /blog | { slug: [] } |
> Note: To access query parameters (e.g. ?q=hello), use the useSearchParams() hook. They are NOT passed as props.
$3
Dinou supports deep nesting of optional segments ([[...]] or [[slug]]), but it enforces a strict No-Gap Rule.
> The Rule: You cannot skip an intermediate optional segment. You can only omit parameters if they are at the end of the URL.
#### ✅ Allowed: "Trailing Omission"
You can leave optional segments undefined, but only if they are the last ones in the structure.
- Structure: src/inventory/[[warehouse]]/[[aisle]]/page.tsx
| URL | Result (params) | Status |
| :------------------- | :------------------------------------------- | :------------------------------ |
| /inventory/main/a1 | { warehouse: "main", aisle: "a1" } | ✅ Full |
| /inventory/main | { warehouse: "main", aisle: undefined } | ✅ Valid (Last one omitted) |
| /inventory | { warehouse: undefined, aisle: undefined } | ✅ Valid (All omitted) |
#### ❌ Forbidden: "Intercalated Undefined" (Gaps)
It is not possible to define a later segment while skipping an earlier one.
- Structure: src/inventory/[[warehouse]]/[[aisle]]/page.tsx
| Goal | Result |
| :-------------------------------------- | :------------------------------------------------------------------------------------- |
| Skip warehouse but define aisle | ❌ Forbidden: You cannot provide an aisle without providing a warehouse first. |
#### Catch-all Constraints
Due to their "greedy" nature (consuming the rest of the URL), Catch-all segments ([...] and [[...]]) must always be the terminal (last) segment of a route definition. You cannot place other static or dynamic folders inside a Catch-all folder.
---
$3
#### Route Groups (folder)
Folders wrapped in parentheses are omitted from the URL path. This is useful for organizational purposes.
- src/(auth)/login/page.jsx → /login
- src/(marketing)/about/page.jsx → /about
- src/(marketing)/(nested)/about/page.jsx → /about
Why use them?
Route Groups allow you to keep your project structure logical without affecting the public URL structure. For example, grouping all authentication-related routes together.
#### Parallel Routes @slot
You can define slots (e.g., @sidebar, @header) to render multiple pages in the same layout simultaneously.
- src/dashboard/@sidebar/page.jsx
- src/dashboard/(group-a)/@bottom/page.jsx
- src/dashboard/layout.jsx → Receives sidebar and bottom as props.
> Note: Slots must be located in the same logical folder as the layout they serve.
Why use them?
Parallel routes allow independent UI sections and Error Containment:
1. Server Component with error.jsx: If the slot fails, only that specific slot renders the error UI.
2. Server Component without error.jsx: If the slot fails, it renders null safely.
3. Client Component: Without an explicit React Error Boundary, an unhandled error here will crash the entire page.
$3
#### Using (Recommended)
The component provides optimized client-side transitions with automatic prefetching.
`jsx
"use client";
import { Link } from "dinou";
export default function Menu() {
return (
);
}
`
> Note: Standard HTML tags also trigger client-side soft navigation via global event delegation in Dinou, but they lack the smart features (prefetching, fresh prop) provided by the component.
#### Programmatic Navigation
Use the useRouter hook inside Client Components ("use client").
`jsx
"use client";
import { useRouter } from "dinou";
export default function Controls() {
const router = useRouter();
return (
{/ Soft Reload: Refetches server data without a browser refresh /}
);
}
`
Layouts & Hierarchical Rendering
Dinou uses a nested routing system. Layouts, Error pages, and Not Found pages cascade down the directory hierarchy.
$3
Layouts wrap pages and child layouts. They persist across navigation, preserving state and preventing unnecessary re-renders.
A layout component receives a children prop, params (route parameters), and any parallel slot (e.g., sidebar) defined in the same folder scope.
> Note: searchParams are NOT passed to layouts.
`jsx
// src/dashboard/layout.jsx
export default async function Layout({ children, params, sidebar }) {
return (
Dashboard for {params.teamId}
{children}
);
}
`
Layouts are nested by default. A page at src/dashboard/settings/page.jsx will be wrapped by:
1. src/layout.jsx (Root Layout)
2. src/dashboard/layout.jsx (Dashboard Layout)
3. src/dashboard/settings/page.jsx (The Page)
Fetching data in Layouts:
The Root Layout that applies to a specific page can receive additional props by defining a getProps function in a page_functions.ts file located alongside the page:
`typescript
// src/foo/bar/page_functions.ts
export async function getProps({ params }) {
// fetch data using route params
const data = await fetchData(params.id);
// 'layout' props are injected into the Root Layout for this page
return { page: { data }, layout: { title: data.name } };
}
`
$3
Create an error.jsx file to define an error page for a route segment. If a page throws an error (Server or Client not controlled by an Error Boundary), Dinou looks for the closest error.jsx in the directory hierarchy (bubbling up).
error.jsx receives error (object) and params as props.
`jsx
// Error pages can be Client Components or Server Components
"use client";
export default function Page({ error, params }) {
return (
Something went wrong in {params.slug}!
{${error.name}: ${error.message}}
{/ error.stack is only defined in development, not in production /}
{error.stack && (
{error.stack}
)}
);
}
`
$3
Create a not_found.jsx file to customize the 404 UI. Like errors, Dinou renders the closest not_found.jsx found traversing up from the requested URL.
not_found.jsx receives params as a prop. To access search parameters, use useSearchParams().
$3
Sometimes you need to break out of the standard nested hierarchy (e.g., a Landing Page that shouldn't share the App Layout). Dinou uses "Flag Files" (empty files with no extension) to control this behavior.
Place these files in the same directory as your component to activate the behavior.
| Flag File | Applies To | Description |
| :-------------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| reset_layout | layout.jsx | Resets the layout tree. This layout becomes the new Root, ignoring all parent layouts. Perfect for separating Marketing pages from App pages. |
| no_layout | page.jsx, error.jsx, not_found.jsx | Prevents the component from being wrapped by _any_ layout in the hierarchy. |
| no_layout_error | error.jsx | Specifically prevents layouts only for the Error page. |
| no_layout_not_found | not_found.jsx | Specifically prevents layouts only for the Not Found page. |
#### Example: Isolate a Landing Page
If you have a marketing page at src/marketing/page.jsx and you don't want it to inherit the Root Layout:
1. Create src/marketing/layout.jsx (Your marketing layout).
2. Create an empty file named reset_layout inside src/marketing/.
3. Result: src/marketing/page.jsx will only use the marketing layout, ignoring the global root layout.
Data Fetching & Rendering
Dinou leverages React 19 Server Components to allow direct data access on the server without sending that logic to the client.
$3
You can define a React Server Component by using an async function.
`jsx
// src/blog/page.jsx
import db from "@/lib/db";
export default async function Page() {
const posts = await db.query("SELECT * FROM posts");
return (
{posts.map((post) => (
- {post.title}
))}
);
}
`
$3
Dinou employs a Zero-Config Hybrid Model. You do not need to configure pages as "Static" or "Dynamic" manually. The framework decides at runtime:
1. Static (SSG): Pages are pre-rendered at start time by default.
2. Dynamic (SSR): If a page utilizes request-specific APIs (Cookies, Headers, Search Params), it automatically opts out of static generation and renders on demand.
`jsx
import { getContext } from "dinou";
export default async function Profile() {
const ctx = getContext();
if (!ctx) return null;
// accessing cookies automatically switches this page to Dynamic Rendering (SSR)
const token = ctx.req.cookies.session_token;
const user = await fetchUser(token);
return Hello, {user.name}
;
}
`
$3
You can enable ISR to update static pages in the background without rebuilding the entire site. Export a revalidate function from your page_functions.ts.
`typescript
// src/blog/page_functions.ts
// This page will regenerate at most once every 60 seconds
export function revalidate() {
return 60000; // time in milliseconds
}
`
$3
To add interactivity (useState, useEffect, event listeners), place the "use client" directive at the top of your file.
`jsx
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return ;
}
`
Server Functions (
"use server") & Enhanced Suspense
Dinou supports Server Functions, allowing you to call server-side logic directly from your Client Components like a Remote Procedure Call (RPC). A unique feature of Dinou is that Server Functions can return rendered Components (both Server or Client Components), not just JSON data.
1. Define a function with "use server" at the top of the file.
2. Import and call it from any component.
`jsx
// src/server-functions/get-post.jsx
"use server";
import db from "./db";
import Post from "@/components/post.jsx";
export async function getPost(postId) {
const data = await db.query("SELECT * FROM posts WHERE id = ?", [postId]);
return ;
}
`
$3
When used in a Client Component, you can use react-enhanced-suspense to automatically re-fetch the Server Function when a key changes.
`jsx
// src/[id]/page.jsx
"use client";
import { getPost } from "@/server-functions/get-post";
import Suspense from "react-enhanced-suspense";
export default function Page({ params: { id } }) {
return (
get-post-${id}}>
{() => getPost(id)}
);
}
`
How react-enhanced-suspense works:
- Standard Mode: If only children (as React Nodes) and fallback are passed, it behaves exactly like React's native .
- Enhanced Mode: If you provide a resourceId prop and children is a function, the promise returned by that function is automatically re-evaluated whenever resourceId changes. This is perfect for handling data dependencies without useEffect.
$3
In Server Components, you can simply wrap the async call to stream the result to the client as soon as it is ready.
`jsx
// src/[id]/page.jsx
import { getPost } from "@/server-functions/get-post";
import Suspense from "react-enhanced-suspense";
export default async function Page({ params: { id } }) {
return (
{/ Behaves like native Suspense (Streaming) /}
{getPost(id)}
);
}
`
$3
Server Functions can also be used as Server Actions by passing them to the action prop of a javascript
import { useSearchParams } from "dinou";
export default function SearchPage() {
const searchParams = useSearchParams();
const query = searchParams.get("q");
return Result: {query};
}
`
Behavior & Static Generation (Bailout):
| Component Type | Behavior during Build | Result |
| :------------------- | :------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| Server Component | Accessing this hook triggers a Static Bailout. | The page opts out of SSG and switches to Dynamic Rendering (SSR) on demand. |
| Client Component | Does NOT trigger a bailout. | The page remains Static (SSG). The initial HTML renders with empty params, and the browser updates values upon hydration. |
> ⚠️ Client Component Warning:
> If used in a Client Component on a static page, be aware of Hydration Mismatches. The server renders with empty params (since they don't exist at build time), but the browser renders with the real URL.
> Recommendation: If the initial UI depends heavily on params, pass them as props from a Server Component to force Dynamic Rendering.
Common Methods:
- .get(name): Returns the first value.
- .getAll(name): Returns all values (useful for ?id=1&id=2).
- .has(name): Checks existence.
- .toString(): Returns the query string.
#### usePathname()
Returns the current URL pathname as a string (e.g., /blog/post-1).
#### useRouter() (Client Only)
Provides programmatic navigation methods.
- Methods: .push(href), .replace(href), .back(), .forward(), .refresh().
- Note: Only works inside Client Components ("use client").
#### useNavigationLoading() (Client Only)
Returns a boolean indicating if a client-side navigation is in progress.
---
$3
#### getContext()
Retrieves the request/response context. Server Components Only.
- Returns: { req, res }.
- req: headers, cookies, query, path, method.
- res: status(), setHeader(), redirect(), cookie(), clearCookie().
#### ⚠️ Security Warning: getContext in Client Components
While getContext() technically works during the Server-Side Rendering (SSR) phase of Client Components, using it directly inside a Client Component is strongly discouraged.
`javascript
"use client";
import { getContext } from "dinou";
// ❌ DANGEROUS PATTERN
export default function UserProfile() {
const ctx = getContext(); // Runs on server during SSR
return {ctx.req.headers["authorization"]};
// ⚠️ The sensitive header is now baked into the public HTML source code!
}
`
Risks:
1. Data Leak: Any data read from getContext during SSR is serialized into the initial HTML. If you mistakenly render sensitive data (like tokens or internal headers), it will be visible in the page source (View Source), even if React hydration fails later.
2. Hydration Mismatch: The browser execution will fail because getContext is not available in the browser, causing the UI to break or flicker.
✅ Correct Pattern:
Fetch sensitive data in a Server Component and pass only the necessary, safe fields as props.
`javascript
// src/profile/page.tsx (Server Component)
import { getContext } from "dinou";
import ClientProfile from "./client-profile";
export default function Page() {
const ctx = getContext();
// Extract ONLY what is safe for the client
const safeUser = { name: ctx.req.cookies.username };
return ;
}
`
---
$3
Export these functions from page_functions.{ts,js} to configure the associated page.tsx.
#### getStaticPaths()
Defines paths for Static Site Generation (SSG).
- Returns: Array.
`typescript
export function getStaticPaths() {
return [{ slug: "foo", id: "bar" }];
}
`
#### getProps({ params })
Async function to fetch data on the server and pass it as props to the Page component and to the Root Layout (if exists).
- Receives: Object with resolved params.
- Returns: Object with the props.
`typescript
export async function getProps({ params }) {
const data = await db.getItem(params.id);
return { page: { item: data }, layout: { title: data.title } }; // Available as props in page.tsx and Root Layout of this particular page.
}
`
#### revalidate()
Sets the Incremental Static Regeneration (ISR) time in milliseconds.
- Returns: Number (ms). 0 means no revalidate (always the same static page).
`typescript
export function revalidate() {
return 60000;
} // Regenerate static page every 1 minute
`
#### dynamic()
Whether to bypass static generation or not (e.g. use dynamic rendering).
- Returns: true, false.
`typescript
export function dynamic() {
return true;
}
`
---
$3
Dinou recognizes specific filenames to build the routing hierarchy.
#### Route Components
| Filename | Environment | Description |
| :-------------------------- | :--------------- | :--------------------------------------------- |
| page.{jsx,tsx,js,ts} | Server or Client | The unique UI for a route. |
| layout.{jsx,tsx,js,ts} | Server or Client | Wraps the page and children segments. |
| error.{jsx,tsx,js,ts} | Server or Client | UI for 500 errors within the segment. |
| not_found.{jsx,tsx,js,ts} | Server or Client | UI for 404 not found pages within the segment. |
#### Layout Control Flags (Empty Files)
Create these empty files to alter how layouts apply to a specific route directory.
| Filename | Effect |
| :-------------------- | :--------------------------------------------------------------------------------------------------- |
| no_layout | The page.tsx in this folder will NOT be applied any layout. |
| reset_layout | Resets the layout hierarchy. The first layout found from within this folder becomes the Root Layout. |
| no_layout_error | The error.tsx in this folder will render without any layout. |
| no_layout_not_found | The not-found.tsx in this folder will render without any layout. |
🎨 Favicons
To add a favicon to your application:
1. Generate your assets using a tool like favicon.io.
2. Unzip the downloaded folder.
3. Rename the folder to favicons and place it in the root of your project.
4. Update your root layout.tsx (or page.tsx) to include the references in the tag:
`typescript
"use client";
import type { ReactNode } from "react";
export default function Layout({ children }: { children: ReactNode }) {
return (
Dinou App
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
{children}
);
}
`
🔐 Environment Variables (
.env)
Dinou automatically loads environment variables for server-side code (Server Components, Server Functions, and getProps).
1. Create a .env file in the root of your project.
2. Add .env to your .gitignore to prevent exposing sensitive keys.
`bash
.env
API_SECRET=my_secret_value
DB_HOST=localhost
`
💅 Styles (Tailwind, CSS Modules, & Global CSS)
Dinou supports Tailwind CSS, CSS Modules (.module.css), and standard Global CSS (.css) out of the box.
Dinou bundles all your styles into a single file served at /styles.css. You must manually link this file in your root layout or page.
$3
Add the link tag to the of your root component:
`typescript
`
$3
src/layout.tsx (Client Component example):
`typescript
"use client";
import type { ReactNode } from "react";
import "./globals.css"; // Import global styles here
export default function Layout({ children }: { children: ReactNode }) {
return (
Dinou App
{/ Favicons omitted for brevity /}
{children}
);
}
`
src/globals.css (Tailwind setup):
`css
@import "tailwindcss";
.custom-bg {
background-color: purple;
}
`
src/page.tsx (Using CSS Modules):
`typescript
"use client";
import styles from "./page.module.css";
export default function Page() {
return (
text-red-500 custom-bg ${styles.underlined}}>
Hello World!
);
}
`
src/page.module.css:
`css
.underlined {
text-decoration: underline;
}
`
src/css.d.ts (TypeScript support for modules):
`typescript
declare module "*.module.css" {
const classes: { [key: string]: string };
export default classes;
}
`
🖼️ Assets & Media
You can import media files directly into your components. Dinou supports a wide range of extensions:
- Images: .png, .jpg, .jpeg, .gif, .svg, .webp, .avif, .ico, .mjpeg, .mjpg
- Audio/Video: .mp4, .webm, .ogg, .mov, .avi, .mkv, .mp3, .wav, .flac, .m4a, .aac
Usage:
`typescript
// src/component.tsx
"use client";
import logo from "./logo.png";
export default function Component() {
return
;
}
`
TypeScript Configuration:
To avoid type errors, create a declaration file (e.g., src/assets.d.ts):
`typescript
declare module "*.png" {
const value: string;
export default value;
}
// Repeat for other extensions used (jpg, svg, mp4, etc.)
`
> Customization: If you need to support additional extensions, you can eject Dinou and modify dinou/core/asset-extensions.js.
🔗 Import Aliases (
@/)
Dinou supports import aliases (e.g., import Button from "@/components/Button"). Configure paths in your tsconfig.json.
tsconfig.json (TypeScript):
`json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/": ["src/"]
},
"allowJs": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true
},
"include": ["src"]
}
`
⚡ Bundlers & Running the App
Dinou is flexible and integrates with three major bundlers: esbuild (default), Rollup, and Webpack.
$3
Starts the development server with hot reloading. Files are emitted to the public folder.
| Command | Bundler | Description |
| :-------------------- | :---------- | :------------------------- |
| npm run dev | esbuild | Default. Fastest startup. |
| npm run dev:esbuild | esbuild | Explicit esbuild command. |
| npm run dev:rollup | Rollup | Uses Rollup for bundling. |
| npm run dev:webpack | Webpack | Uses Webpack for bundling. |
$3
Compiles the application for production. Files are emitted to the dist3 folder.
| Command | Bundler |
| :---------------------- | :--------------------- |
| npm run build | esbuild (Default) |
| npm run build:esbuild | esbuild (Explicit) |
| npm run build:rollup | Rollup |
| npm run build:webpack | Webpack |
$3
Runs the built application from the dist3 folder.
| Command | Description |
| :---------------------- | :---------------------------------- |
| npm start | Same as npm run start:esbuild. |
| npm run start:esbuild | Use it after building with esbuild. |
| npm run start:rollup | Use it after building with Rollup. |
| npm run start:webpack | Use it after building with Webpack. |
⏏️ Eject Dinou
If you need full control over the configuration or internal logic, you can "eject" the framework.
`bash
npm run eject
or
npx dinou eject
`
This will copy the entire Dinou core into a dinou/ folder in your project root, allowing you to modify build scripts, server logic, and configuration files directly.
🚀 Deployment
Dinou apps can be deployed to any platform supporting Node.js, provided you can pass custom flags.
$3
Dinou works seamlessly on DigitalOcean. It allows full control over the runtime command, essential for the required --conditions react-server flag.
$3
Netlify is currently incompatible because it does not support passing custom Node.js flags (--conditions react-server) during runtime.
$3
Ensure your platform allows customization of the start command. Your start command should look like:
`bash
node --conditions react-server ...
`
_(Or use npm start` if your package.json scripts are configured correctly)._