App Router
Introduction
App router is mixture of multiple page and single page application
Each path has their own html page but when using
next/link
, the javascript will be loaded instead of html fileThe initial page will be rendered on server, but for user interaction (e.g:
useState
,useEffect
) , the page is needed to be hydrated with javascript file
Server & Client Side Component
When html page of Nextjs is loaded, the static content and data of of server component will be pre-rendered and fetched
The data of client component will then be hydrated through built javascript file
Example
"use client";
// import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
// export const metadata: Metadata = {
// title: "Create Next App",
// description: "Generated by create next app",
// };
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
client layout
{children}
</body>
</html>
);
}
import ClientComponent from "./components/ClientComponent";
import ServerComponent from "./components/ServerComponent";
export default function Home() {
return (
<>
<ClientComponent/>
<ServerComponent/>
</>
);
}
"use client";
import React from "react";
// Next.js fetch API in action
async function loadPosts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
// return res.json();
return ["this is client component"];
}
const ClientComponent = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [posts, setPosts] = React.useState<any[]>([]);
React.useEffect(() => {
loadPosts().then((posts) => setPosts(posts));
}, []);
return (
<div className="post-list">
Client Component
{posts.map((post) => (
<div key={post} className="post-listing">
<p className="post-body">{post}</p>
</div>
))}
</div>
);
};
export default ClientComponent;
// Next.js fetch API in action
async function loadPosts() {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
/*
* Since this is a server component, the below message
* won't be displayed in the browser's dev console.
*/
console.log("Server Component fetching");
// return res.json();
return ["this is server component"];
}
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
const ServerComponent = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const posts:any[] = await loadPosts();
return (
<div className="post-list">
Server Component
{posts.map((post) => (
<div key={post} className="post-listing">
<p className="post-body">{post}</p>
</div>
))}
</div>
);
};
export default ServerComponent;
Result


Link Navigation
import Link from "next/link"
import ClientComponent from "../components/ClientComponent"
const OtherPage = () => {
return (
<div>
<ClientComponent/>
<Link href={"/"}>Back Home</Link>
</div>
)
}
export default OtherPage

If using
Link
instead ofa
tag for navigation. On UI level, it will be shown asa
tag, but it will execute client side navigation behind the scene,If the page contains server component, only RSC payload will be fetched, which is the result of server-side rendering and the position of client-side component with corresponding javascript file
Backend API
Nextjs can be used to act as full stack development, here is the example of implementing backend part. The file must be
route.ts/js
import { NextResponse } from 'next/server';
export function GET() {
const randomNumber = Math.floor(Math.random() * 1000);
const message = { message: `${randomNumber}` };
return NextResponse.json(message);
}
// Next.js fetch API in action
async function loadPosts() {
const response = await fetch('http://localhost:3000/api');
const json = await response.json();
return await json.message;
}
const ServerComponent = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const posts:any = await loadPosts();
return (
<div className="post-list">
Server Component
<div className="post-listing">
<p className="post-body">{posts}</p>
</div>
</div>
);
};
export default ServerComponent;
Caching

When using
fetch
api on Nextjs, there are several caches existing - request memoization, data cache
Request memoization

If you need to use the same data across a route, e.g using the same component, you do not have to fetch data in every time. Instead, it will fetch data once.
The cache lasts the lifetime of a server request until the React component tree has finished rendering.
import Link from "next/link";
import ServerComponent from "./components/ServerComponent";
export default function Home() {
return (
<>
<ServerComponent/>
<ServerComponent/>
<Link href="/other">Go to other</Link>
</>
);
}
// Next.js fetch API in action
async function loadPosts() {
const response = await fetch('http://localhost:3000/api',
{cache:"default", next:{ revalidate: 0 }}
);
const json = await response.json();
return await json.message;
}
const ServerComponent = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const posts:any = await loadPosts();
return (
<div className="post-list">
Server Component
<div className="post-listing">
<p className="post-body">{posts}</p>
</div>
</div>
);
};
export default ServerComponent;

To disable request memoization, we need integrate with
AbortController
// Next.js fetch API in action
async function loadPosts() {
const { signal } = new AbortController();
const response = await fetch('http://localhost:3000/api',
{signal, cache:"default", next:{ revalidate: 0 }}
);
const json = await response.json();
return await json.message;
}
const ServerComponent = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const posts:any = await loadPosts();
return (
<div className="post-list">
Server Component
<div className="post-listing">
<p className="post-body">{posts}</p>
</div>
</div>
);
};
export default ServerComponent;

Data Cache
If fetch data with caching, the page will be built as static content (Static Generation), otherwise, the page will be built as dynamic
Cache options
Configure how the request should interact with Next.js Data Cache.
fetch(`https://...`, { cache: 'default' | 'force-cache' | 'no-store' })
default
: Nextjs fetching will be treated same asforce-cache
, unlessrevaildate
is specifiedno-store
: Next.js fetches the resource from the remote server on every request without looking in the cache, and it will not update the cache with the downloaded resource.force-cache
: Next.js looks for a matching request in its Data Cache.If there is a match and it is fresh, it will be returned from the cache.
If there is no match or a stale match, Next.js will fetch the resource from the remote server and update the cache with the downloaded resource.
Incremental Static Regeneration (ISR)

The data of cache will be stale after a period of time, and the data will be obtained from data source rather than cache
// Next.js fetch API in action
async function loadPosts() {
const { signal } = new AbortController();
const response = await fetch('http://localhost:3000/api',
{signal, next:{ revalidate: 5 }}
);
const json = await response.json();
return await json.message;
}
const ServerComponent = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const posts:any = await loadPosts();
return (
<div className="post-list">
Server Component
<div className="post-listing">
<p className="post-body">{posts}</p>
</div>
</div>
);
};
export default ServerComponent;
Layout
The component in the layout file can be shared in the same directory and its nested page file
SideMenu is common use case to put it into layout file
"use client"
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import theme from "./themes/defaultTheme";
import {ThemeProvider} from "@mui/material"
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
// export const metadata: Metadata = {
// title: "Create Next App",
// description: "Generated by create next app",
// };
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider theme={theme}>
main1
{children}
</ThemeProvider>
</body>
</html>
);
}
Not Found & Error Boundary Page
Error page must be client component
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<Link href="/">Return Home</Link>
</div>
)
}
'use client' // Error boundaries must be Client Components
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
Route Folder & File naming
(Folder name)
can be used without affecting the pathpage.tsx
refer to the page[slug]
refer to dynamic page
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug;
return (
<div>My Post: {slug}</div>
)
}
Loading
The data fetching support
Suspense
of react
import { Suspense } from "react";
import axios from "axios";
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
async function getMovies() {
const {data} = await axios.get(
`https://fakestoreapi.com/products`
);
// await new Promise((resolve) => setTimeout(resolve, 2000));
return data;
}
const results = await getMovies();
console.log(results);
const slug = (await params).slug;
return (
<Suspense>
<div>My Post: {slug}</div>
</Suspense>
)
}
import React from 'react'
const Loading = () => {
return (
<div>Loading</div>
)
}
export default Loading
Parallel Route
Parallel Routes allows you to simultaneously or conditionally render one or more pages within the same layout.
Each route can actually be treated as component on layout
Each route nested route pattern must be the same
import React from 'react'
const UserPage = () => {
return (
<div>User Page</div>
)
}
export default UserPage
import React from 'react'
import TestCompoent from './components/test'
import Link from 'next/link'
const Main = () => {
return (
<div>
Main Page
</div>
)
}
export default Main
"use client"
import React from "react"
export default function Layout({
user,
children, // will be a page or nested layout
}: {
children: React.ReactNode
user: React.ReactNode
}) {
return (
<>
/* User */
{user}
/* Main */
{children}
</>
)
}
Last updated
Was this helpful?