Getting Started
  • Introduction
  • Installation
  • About
Core Concepts
  • Routing System
  • Page Functions
  • Data Fetching
  • Server Components
  • Client Components
Features
  • Dynamic & Query Parameters
  • Navigation
  • Styles & CSS
  • Images
  • Favicons
Configuration
  • Environment Variables
  • Import Aliases
  • Ejecting
dinou logodinou
docs
npmGitHub

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)

This option is useful for SSG (Static Site Generated) pages. When used with dynamic rendering (no SSG) it increases the FCP (First Contentful Paint), that is, when the user sees something rendered on the page.

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.

This pattern works equally well with 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.

This pattern works equally well with 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.

Unlike non-optional dynamic routes, the base route /optional also renders the same page component. The name param will be undefined in that case.
This pattern works equally well with 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.

If the user visits /catch-all (i.e., with no segments), thenot_found.tsx page will be rendered instead — the most nested one available in the route hierarchy.
This pattern works equally well with 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.

Unlike regular catch-all routes, this variant also handles the base path /catch-all-optional by rendering the page instead of a not_found.tsx fallback. The params.names array will be empty in that case.
This pattern works equally well with page.tsx being a Server Component.

On This Page

Fetching with SuspenseServer Fetching without SuspenseSSG ConsiderationsStatic RouteDynamic RouteOptional Dynamic RouteCatch-All Dynamic RouteOptional Catch-All Dynamic Route