Data Fetching
Learn how to fetch data in dinou using Suspense and Server Functions.
Fetching data with Suspense
We have already seen that data can be fetched on the server with the getProps
function or within the body of a Server Component, but this needs to be accompanied of a mechanism of SSG of the page/s to not increase the FCP.
There is an alternative that do not increase FCP even when rendering dynamically and that is to use Suspense
for data fetching, either in the server and in the client.
Post Component
// src/posts/post.tsx
"use client";
export type PostType = {
title: string;
content: string;
};
export default function Post({ post }: { post: PostType }) {
return (
<>
<h1>{post.title}</h1>
<div>{post.content}</div>
</>
);
}
Server Function
// src/posts/get-post.tsx
"use server";
import Post from "./post";
import type { PostType } from "./post";
export async function getPost() {
const post = await new Promise<PostType>((r) =>
setTimeout(
() => r({ title: "Post Title", content: "Post content" }),
1000
)
);
return <Post post={post} />;
}
Page with Suspense
// src/posts/page.tsx
"use client";
import { Suspense } from "react";
import { getPost } from "./get-post";
import Post from "./post";
import type { PostType } from "./post";
export default function Page({ data }: { data: string }) {
const getPost2 = async () => {
const post = await new Promise<PostType>((r) =>
setTimeout(
() => r({ title: "Post Title2", content: "Post content2" }),
1000
)
);
return <Post post={post} />;
};
return (
<>
<Suspense fallback={<div>Loading...</div>}>{getPost()}</Suspense>
<Suspense fallback={<div>Loading2...</div>}>{getPost2()}</Suspense>
</>
);
}
The same can be done with page.tsx
being a Server Component.
Fetching data in the server without Suspense (revisited)
The recommended way to use it is with page.tsx
being a Client Component and defining a page_functions.ts
with getProps
function defined and exported. The other option is to use a Server Component for page.tsx
instead of a Client Component and do the fetch in the body of the Server Component (async
function) or, what is equivalent, use the getProps
function defined and exported in page_functions.ts
too.
Pages in static routes (e.g. /some/route
) are statically generated (SSG) if no dynamic
function returning true
is defined and exported in a page_functions.ts
. Therefore, statically generated pages for static routes will be served if no query params are present in the request. If there are query params pages will be served dynamically.
Pages in dynamic routes (e.g. /[id]
, or /[[id]]
, [...id]
, [[...id]]
) are statically generated (SSG) if no dynamic
function returning true
is defined and exported in a page_functions.ts
, for those values of the dynamic param returned by function getStaticPaths
defined and exported in page_functions.ts
. Again, if query params are used in the request of the page, then it will be rendered dynamically, affecting the FCP (increasing it). Or those requests using dynamic params not returned by getStaticPaths
will also be rendered dynamically.
SSG Considerations
Static Route Example
// src/static/page.tsx
"use client";
export default function Page({ data }: { data: string }) {
return <>{data}</>;
}
// src/static/page_functions.ts
export async function getProps() {
const data = await new Promise<string>((r) =>
setTimeout(() => r(`data`), 2000)
);
return { page: { data }, layout: { title: data } };
}
In this case the static generated route will be /static
. If query params are passed to the route (e.g. /static?some-param
) the route will be rendered dynamically, increasing the FCP by 2 secs (2000 ms) in this particular case.
page.tsx
being a Server Component.Dynamic Route Example
// src/dynamic/[name]/page.tsx
"use client";
export default function Page({
params: { name },
data,
}: {
params: { name: string };
data: string;
}) {
return (
<>
{name}
{data}
</>
);
}
// src/dynamic/[name]/page_functions.ts
export async function getProps(params: { name: string }) {
const data = await new Promise<string>((r) =>
setTimeout(() => r(`Hello ${params.name}`), 2000)
);
return { page: { data }, layout: { title: data } };
}
export function getStaticPaths() {
return ["albert", "johan", "roger", "alex"];
}
In this case statically generated routes will be /dynamic/albert
, /dynamic/johan
, /dynamic/roger
, and /dynamic/alex
. /dynamic
will render not_found.tsx
page (the more nested one existing in the route hierarchy) if no page.tsx
is defined in this route. Any other route as /dynamic/other-name
will be rendered dynamically, increasing the FCP by 2 secs (2000 ms) in this particular case.
page.tsx
being a Server Component.Optional Dynamic Route Example
// src/optional/[[name]]/page.tsx
"use client";
export default function Page({
params: { name },
data,
}: {
params: { name: string };
data: string;
}) {
return (
<>
{name}
{data}
</>
);
}
// src/optional/[[name]]/page_functions.ts
export async function getProps(params: { name: string }) {
const data = await new Promise<string>((r) =>
setTimeout(() => r(`Hello ${params.name ?? ""}`), 2000)
);
return { page: { data }, layout: { title: data } };
}
export function getStaticPaths() {
return ["albert", "johan", "roger", "alex"];
}
In this case, statically generated routes will be /optional
, /optional/albert
, /optional/johan
, /optional/roger
, and /optional/alex
. Any other route like /optional/other-name
will be rendered dynamically at request time, increasing the FCP by 2 seconds (2000 ms) in this example.
/optional
also renders the same page component. The name
param will be undefined
in that case.page.tsx
being a Server Component.Catch-All Dynamic Route Example
// src/catch-all/[...names]/page.tsx
"use client";
export default function Page({
params: { names },
data,
}: {
params: { names: string[] };
data: string;
}) {
return (
<>
{names}
{data}
</>
);
}
// src/catch-all/[...names]/page_functions.ts
export async function getProps(params: { names: string[] }) {
const data = await new Promise<string>((r) =>
setTimeout(() => r(`Hello ${params.names.join(",")}`), 2000)
);
return { page: { data }, layout: { title: data } };
}
export function getStaticPaths() {
return [["albert"], ["johan"], ["roger"], ["alex"], ["albert", "johan"]];
}
In this case, statically generated routes will be /catch-all/albert
, /catch-all/johan
, /catch-all/roger
, /catch-all/alex
, and /catch-all/albert/johan
. Any other route that starts with /catch-all/
will be rendered dynamically at request time, increasing the FCP by 2 seconds (2000 ms) in this example.
/catch-all
(i.e., with no segments), thenot_found.tsx
page will be rendered instead — the most nested one available in the route hierarchy.page.tsx
being a Server Component.Optional Catch-All Dynamic Route Example
// src/catch-all-optional/[[..names]]/page.tsx
"use client";
export default function Page({
params: { names },
data,
}: {
params: { names: string[] };
data: string;
}) {
return (
<>
{names}
{data}
</>
);
}
// src/catch-all-optional/[[..names]]/page_functions.ts
export async function getProps(params: { names: string[] }) {
const data = await new Promise<string>((r) =>
setTimeout(() => r(`Hello ${params.names.join(",")}`), 2000)
);
return { page: { data }, layout: { title: data } };
}
export function getStaticPaths() {
return [["albert"], ["johan"], ["roger"], ["alex"], ["albert", "johan"]];
}
In this case, statically generated routes will be /catch-all-optional
, /catch-all-optional/albert
, /catch-all-optional/johan
, /catch-all-optional/roger
, /catch-all-optional/alex
, and /catch-all-optional/albert/johan
. Any other route starting with /catch-all-optional/
will be rendered dynamically, increasing the FCP by 2 seconds (2000 ms) in this example.
/catch-all-optional
by rendering the page instead of a not_found.tsx
fallback. The params.names
array will be empty in that case.page.tsx
being a Server Component.