Pages and Layouts
Basic Concepts of Pages and Layouts
Pages
Pages are the primary components of an application, with each page corresponding to a URL path.
Layouts
Layouts allow you to share common UI elements, such as headers, footers, or sidebars, across pages.
Organizing Pages and Layouts
File Structure
In Next.js 14, pages and layouts are typically placed in the pages directory. Each page can have its own layout file.
pages/
├── _app.js
├── _document.js
├── _error.js
├── home/
│ └── index.js
├── about/
│ └── index.js
├── blog/
│ └── [id].js
└── layouts/
├── header.js
├── footer.js
└── defaultLayout.jsDefault Layout (defaultLayout.js)
// pages/layouts/defaultLayout.js
import Header from './header';
import Footer from './footer';
export default function DefaultLayout({ children }) {
return (
<div>
<Header />
<main>{children}</main>
<Footer />
</div>
);
}Header Component (header.js)
// pages/layouts/header.js
export default function Header() {
return (
<header>
<nav>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
</header>
);
}Footer Component (footer.js)
// pages/layouts/footer.js
export default function Footer() {
return (
<footer>
<p>© {new Date().getFullYear()} My Website</p>
</footer>
);
}Home Page (home/index.js)
// pages/home/index.js
import DefaultLayout from '../layouts/defaultLayout';
export default function HomePage() {
return (
<DefaultLayout>
<h1>Welcome to Home Page</h1>
<p>This is the home page content.</p>
</DefaultLayout>
);
}About Page (about/index.js)
// pages/about/index.js
import DefaultLayout from '../layouts/defaultLayout';
export default function AboutPage() {
return (
<DefaultLayout>
<h1>About Us</h1>
<p>This is the about us page content.</p>
</DefaultLayout>
);
}Dynamic Route Page (blog/[id].js)
// pages/blog/[id].js
import DefaultLayout from '../layouts/defaultLayout';
export default function BlogPost({ params }) {
const postId = params.id;
return (
<DefaultLayout>
<h1>Blog Post {postId}</h1>
<p>This is the content of post {postId}.</p>
</DefaultLayout>
);
}
export async function getStaticPaths() {
const posts = ['post1', 'post2', 'post3'];
return {
paths: posts.map((postId) => ({
params: { id: postId },
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const { id } = params;
// Fetch data for the specific post
return {
props: {
id,
},
};
}Summary
- Default Layout: Use
defaultLayout.jsto create a basic layout that includes a header and footer. - Pages: Each page file can import the default layout and wrap its content with it.
- Dynamic Routes: Use
[id].jsfile structure to create dynamic route pages, withgetStaticPathsandgetStaticPropsto generate static parameters and page data.
Dynamic Routes
Basic Concepts of Dynamic Routes
Dynamic routes are URLs that contain variable segments. In Next.js, you can create dynamic routes by using square brackets [ ] in the page file name.
Creating Dynamic Route Pages
To create a dynamic route page in Next.js, create a folder with square brackets in the my-app/pages directory. For example, to create a blog page where each post has a unique ID, structure it as follows:
my-app/
├── pages/
│ └── blog/
│ └── [id].jsapp/pages/blog/[id].js
// app/pages/blog/[id].js
import DefaultLayout from '@/layouts/defaultLayout';
export default function BlogPost({ params }) {
const postId = params.id;
return (
<DefaultLayout>
<h1>Blog Post {postId}</h1>
<p>This is the content of post {postId}.</p>
</DefaultLayout>
);
}
export async function generateStaticParams() {
// Assume fetching post IDs from a database
const posts = ['post1', 'post2', 'post3'];
return posts.map((postId) => ({
id: postId,
}));
}Parsing Dynamic Route Parameters
In the page component, access dynamic route parameters via the params object. In this example, params.id is the id parameter from the dynamic route.
Generating Static Parameters
The generateStaticParams function informs Next.js how to generate static parameters. It returns an array of objects, each representing a value for the dynamic parameter, ensuring Next.js pre-renders all possible dynamic route paths during build.
Generating Static Metadata
To generate specific metadata for each dynamic route, such as page titles or descriptions, use the generateMetadata function.
// app/pages/blog/[id].js
export async function generateMetadata({ params }) {
const postId = params.id;
return {
title: `Post ${postId}`,
description: `This is the description of post ${postId}.`,
};
}Summary
- Dynamic Routes: Use
[id].jsfile structure to create dynamic route pages. - Parse Parameters: Access dynamic route parameters via the
paramsobject. - Generate Static Parameters: Use
generateStaticParamsto generate a list of static parameters. - Generate Metadata: Use
generateMetadatato generate metadata for each dynamic route.
Links and Navigation
Using the Link Component
Next.js provides a Link component to create links that optimize client-side navigation performance.
// pages/home/index.js
import Link from 'next/link';
export default function HomePage() {
return (
<div>
<h1>Welcome to Home Page</h1>
<p>This is the home page content.</p>
<Link href="/about">
<a>About Us</a>
</Link>
</div>
);
}Using the useRouter Hook
You can use the useRouter hook for programmatic navigation.
// pages/home/index.js
import { useRouter } from 'next/router';
export default function HomePage() {
const router = useRouter();
const handleNavigation = () => {
router.push('/about');
};
return (
<div>
<h1>Welcome to Home Page</h1>
<p>This is the home page content.</p>
<button onClick={handleNavigation}>About Us</button>
</div>
);
}Redirects
Using the redirect Function
In Next.js 14, you can use the redirect function to implement page redirects.
// pages/about/index.js
import { redirect } from 'next/navigation';
export default function AboutPage() {
redirect('/home'); // Redirect to the homepage
return null; // Return null to indicate the page will not render
}Using next.config.js for Server-Side Redirects
You can configure server-side redirect rules in the next.config.js file.
// next.config.js
module.exports = {
async redirects() {
return [
{
source: '/old-about',
destination: '/about',
permanent: true,
},
];
},
};Custom App
In Next.js 14, the Pages Router allows you to extend and customize your application using a custom app (_app.js) and custom document (_document.js). These files provide additional functionality, such as global styles, layout management, and page lifecycle hooks.
File Location
The _app.js file is located in the pages directory.
Uses
- Global Layout: Add global layouts or components.
- Page Lifecycle Hooks: Listen to page load and unload events.
- State Management: Manage global state in
_app.js.
// pages/_app.js
import DefaultLayout from '../layouts/defaultLayout';
import 'tailwindcss/tailwind.css'; // Import global styles
function MyApp({ Component, pageProps }) {
return (
<DefaultLayout>
<Component {...pageProps} />
</DefaultLayout>
);
}
MyApp.getInitialProps = async (appContext) => {
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default MyApp;Custom Document
File Location
The _document.js file is also located in the pages directory.
Uses
- HTML Tags: Customize HTML
<head>and<body>tags. - Global Styles: Add global styles.
- SEO: Customize SEO-related meta tags.
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet"
/>
<meta name="description" content="Generated by create next app" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;API Routes
Sending API Requests Using the fetch Function
Next.js 14 supports using the modern browser fetch function to send API requests.
// pages/home/index.js
import { useEffect, useState } from 'react';
export default function HomePage() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => console.error('Error fetching data:', error));
}, []);
return (
<div>
<h1>Welcome to Home Page</h1>
<p>Data: {data?.message}</p>
</div>
);
}Creating an API Route
Create an API route to handle requests and return data.
// pages/api/data.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from the API!' });
}Using useEffect and useState for Asynchronous Data Fetching
In client-rendered pages, use useEffect and useState to handle asynchronous data fetching.
// pages/home/index.js
import { useEffect, useState } from 'react';
export default function HomePage() {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
}
fetchData();
}, []);
return (
<div>
<h1>Welcome to Home Page</h1>
<p>Data: {data?.message}</p>
</div>
);
}Using getServerSideProps for Server-Side Data Fetching
For server-rendered pages, use the getServerSideProps function to fetch data.
// pages/home/index.js
export default function HomePage({ data }) {
return (
<div>
<h1>Welcome to Home Page</h1>
<p>Data: {data.message}</p>
</div>
);
}
export async function getServerSideProps() {
const response = await fetch('/api/data');
const data = await response.json();
return {
props: {
data,
},
};
}Summary
- Using fetch: Use the
fetchfunction in client-rendered pages to fetch data. - Using getServerSideProps: Use
getServerSidePropsin server-rendered pages to fetch data. - Using getStaticProps: Use
getStaticPropsin statically generated pages to fetch data.
Custom Errors
File Location
Custom error pages should be placed in the pages directory and named _error.js.
Uses
- Handle 404 Errors: Display when a user visits a non-existent page.
- Handle Other HTTP Errors: Display different error pages based on HTTP status codes.
// pages/_error.js
import Error from 'next/error';
function CustomErrorPage({ statusCode }) {
return <Error statusCode={statusCode} />;
}
CustomErrorPage.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default CustomErrorPage;Internationalized Routing
Internationalization Setup
Install next-i18next Package
Install the next-i18next package to support internationalization.
npm install next-i18next i18next react-i18next
# or
yarn add next-i18next i18next react-i18nextConfigure next-i18next
Create a next-i18next.config.js file to configure internationalization.
// next-i18next.config.js
const path = require('path');
module.exports = {
i18n: {
locales: ['en', 'zh'], // Supported languages
defaultLocale: 'en', // Default language
localeDetection: false, // Disable automatic language detection
localePath: path.resolve('./public/locales'), // Location for translation files
},
react: {
useSuspense: false, // Disable Suspense to avoid SSR issues
},
};Create Translation Files
Create translation files in the public/locales directory.
public/
└── locales/
├── en/
│ └── common.json
└── zh/
└── common.jsonExample Content
// public/locales/en/common.json
{
"welcome": "Welcome to our website!",
"home": "Home",
"about": "About"
}
// public/locales/zh/common.json
{
"welcome": "欢迎来到我们的网站!",
"home": "首页",
"about": "关于我们"
}Use next-i18next Component
Use the I18nextProvider component from next-i18next in _app.js.
// pages/_app.js
import { I18nextProvider } from 'next-i18next';
import { initI18next } from '../i18n';
import DefaultLayout from '../layouts/defaultLayout';
function MyApp({ Component, pageProps }) {
const i18n = initI18next(pageProps.initialI18nStore, pageProps.locale);
return (
<I18nextProvider i18n={i18n}>
<DefaultLayout>
<Component {...pageProps} />
</DefaultLayout>
</I18nextProvider>
);
}
export default MyApp;Create i18n.js File
Create an i18n.js file to initialize i18next.
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
detection: {
order: ['cookie', 'htmlTag', 'path', 'subdomain'],
caches: ['cookie'],
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
react: {
useSuspense: false,
},
});
export function initI18next(initialStore, initialLanguage) {
i18n.init({
resources: initialStore,
lng: initialLanguage,
});
return i18n;
}Use useTranslation Hook
Use the useTranslation hook in page components to access translated text.
// pages/home/index.js
import { useTranslation } from 'next-i18next';
export default function HomePage() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<nav>
<ul>
<li><a href="/">{t('home')}</a></li>
<li><a href="/about">{t('about')}</a></li>
</ul>
</nav>
</div>
);
}Summary
- Install
next-i18next: Install the necessary packages. - Configure
next-i18next: Set supported and default languages. - Create Translation Files: Create JSON files for each language.
- Use
I18nextProvider: Wrap the app in_app.js. - Use
useTranslationHook: Access translated text in page components.
Middleware
Creating Middleware
The middleware file should be placed at the same level as the pages directory and named middleware.js.
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
// Get the request URL
const { pathname } = request.nextUrl;
// Check if it's the login page
if (pathname.startsWith('/login')) {
return NextResponse.next();
}
// Check if the user is logged in
const sessionCookie = request.cookies.get('session');
if (!sessionCookie) {
// If not logged in, redirect to the login page
return NextResponse.redirect(new URL('/login', request.url));
}
// If logged in, proceed with the request
return NextResponse.next();
}
// Configure middleware matching rules
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Parsing Middleware Configuration
middleware Function
This is the main middleware function, which takes a Request object as a parameter.
NextResponse Object
Use the NextResponse object to control middleware behavior, such as returning a response or redirecting.
config Object
This object specifies which paths the middleware applies to. In this example, API routes and certain static assets are excluded.
Example Explanation
In this example, a simple middleware checks if a user is logged in. If the user tries to access a non-login page without being logged in, the middleware redirects them to the login page.
Using Middleware
Once created, Next.js automatically loads and applies the middleware. You do not need to explicitly reference it in any pages or components.
Summary
- Create middleware.js File: Place the
middleware.jsfile at the same level as thepagesdirectory. - Write Middleware Logic: Use the
middlewarefunction to handle requests. - Use NextResponse: Control middleware behavior with
NextResponse. - Configure Matching Rules: Use the
configobject to specify middleware matching rules.



