Data Fetching
Dinou offers two main strategies for data fetching: traditional with Server Components and advanced with Suspense combined with Server Functions for reactive experiences.
Without Suspense (Traditional Strategy)
Ideal for static pages or data that doesn't change frequently. This strategy blocks rendering until all data is available.
Server Components (Async Data)
Define async functions to fetch data directly in Server Components without sending logic to the client.
// src/blog/page.jsx
import db from "@/lib/db";
export default async function Page() {
const posts = await db.query("SELECT * FROM posts");
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}Fetch data on the server for security and performance. No client-side fetching needed.
Using getProps (Data Injection)
For static pages or route-based data, use getPropsin page_functions.ts to inject data into your pages and layouts.
// src/blog/[slug]/page_functions.ts
export async function getProps(params) {
// 1. Fetch data based on the URL path (e.g., /blog/my-post)
const post = await db.getPost(params.slug);
// 2. Return data.
// 'page' props go to page.jsx
// 'layout' props go to layout.jsx (useful for setting document titles dynamically)
return {
page: { post },
layout: { title: post.title },
};
}Design Note: Parameter vs. Server Context
While getProps only receives the route params as its parameter, you can still read cookies, headers, or query parameters by calling getContext() since the function executes on the server.
Performance Trade-off:
Because getProps blocks the rendering process at request time, the server waits for it to resolve before starting the render. Keep it fast for dynamic routes; pre-rendered static pages (SSG) are unaffected since their data is fetched at server startup. In Incremental Static Generation (ISG), it only blocks the very first request by the first visitor before the page is cached. If you need slow dynamic fetches on dynamic routes, defer them to a Suspense boundary wrapping a Server Function.
With Suspense & Server Functions (React 19 Reactive Strategy)
For fluid user experiences with progressive loading and automatic updates when dependencies change. This strategy combines React 19's native <Suspense> and use() primitives with Server Functions for optimal performance — with zero external libraries.
Client Components with Server Functions (Reactive Updates)
In Client Components, use React 19's native use() hook inside a <Suspense> boundary. Stabilizing the Server Function promise with useMemo avoids re-fetching on every render, while changing the key prop (or id) triggers the Suspense fallback smoothly without full-page reloads.
// src/[id]/page.jsx
"use client";
import { Suspense, use, useMemo } from "react";
import { getPost } from "@/server-functions/get-post";
function PostContent({ promise }) {
// Pure React 19: unroll the Flight promise inside Suspense
const post = use(promise);
return <>{post}</>;
}
export default function Page({ params: { id } }) {
// Pure React 19: stabilize the Server Function promise across renders based on 'id'
const postPromise = useMemo(() => getPost(id), [id]);
return (
<Suspense key={id} fallback={<p>Loading post...</p>}>
<PostContent promise={postPromise} />
</Suspense>
);
}React 19 Native Suspense & use()
Zero External Libraries: React 19 eliminates the need for third-party enhanced suspense packages. By pairing the native use() hook with useMemo and a keyed <Suspense key={id}>, React handles the full async lifecycle and pending state automatically.
Key-Driven Invalidation: Changing the key prop resets the Suspense boundary and re-triggers the fallback smoothly whenever parameters change.
Using React's standard key prop on <Suspense> acts as the native invalidation boundary. When id changes, React cleanly unmounts the previous view and displays the fallback while the new promise resolves.
Server Components with Server Functions (Streaming)
In Server Components, wrap Server Function calls in native <Suspense> to stream results to the client as they become available. Server Functions execute on the server in Node.js, and Suspense streams the results incrementally.
// src/[id]/page.jsx
// Server Component
import { Suspense } from "react";
import { getPost } from "@/server-functions/get-post";
async function Post({ id }) {
// Executes on the server and returns a rendered Component
return await getPost(id);
}
export default function Page({ params: { id } }) {
return (
<div>
<Suspense fallback={<p>Loading post...</p>}>
<Post id={id} />
</Suspense>
</div>
);
}use(promise) hook inside <Suspense key={id}>, reacting cleanly when dependencies change without manual effects.await getPost(id) directly within an async child component wrapped in native <Suspense> to stream HTML chunks progressively over the wire.Performance Tip: Static Shell + Dynamic Fetching
Suspense. The browser downloads the static page shell instantly, and the Server Function fetches the dynamic parts on-demand.