Route Definitions
Basic Routes
Create a folder in the app directory, such as dashboard, and add a file named page.js to define a route for /dashboard.
// app/dashboard/page.js
export default function Dashboard() {
return <div>Dashboard Page</div>;
}Dynamic Routes
Dynamic routes allow rendering different pages based on parameters, such as a blog post with a unique ID.
// app/posts/[id]/page.js
export default function Post({ id }) {
return <div>Post {id}</div>;
}
export async function generateStaticParams() {
const posts = [1, 2, 3]; // Assume these are post IDs fetched from a database
return posts.map((id) => ({ id: id.toString() }));
}Nested Routes
Create a nested directory structure to represent nested routes.
// app/dashboard/settings/page.js
export default function Settings() {
return <div>Settings Page</div>;
}Layouts and Loading
Layouts allow sharing common layouts across multiple pages, and loading states can be defined.
// app/dashboard/layout.js
export default function DashboardLayout({
children, // Will be a page or nested layout
}) {
return (
<section>
<h1>Dashboard</h1>
{children}
</section>
);
}Error Boundaries
Error boundaries catch and handle errors in child components.
// app/dashboard/error.js
export default function Error({ error, reset }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Server Components
Server components enable rendering components on the server, which is beneficial for SEO and performance optimization.
// app/dashboard/server-component.js
export default async function ServerComponent() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>Data from server: {json.text}</div>;
}Route Groups
Route groups organize related pages, allowing the application of shared layouts or error-handling logic.
// app/dashboard/[...group].js
export default function Group({ params }) {
return <div>Group: {params.group.join('/')}</div>;
}
export async function generateStaticParams() {
return [
{ group: ['settings'] },
{ group: ['preferences'] },
];
}Route Handlers
Route handlers define API paths for specific routes, handling HTTP methods like GET, POST, PUT, DELETE, etc.
// app/api/dashboard/route.js
export async function GET(request) {
return new Response(JSON.stringify({ message: 'Hello from the dashboard!' }));
}
export async function POST(request) {
const body = await request.json();
console.log(body);
return new Response(JSON.stringify({ message: 'Data received' }));
}Fetching Data
Next.js provides a fetch method to simplify data fetching, with the revalidate parameter to control caching strategies.
// app/dashboard/page.js
import { revalidateTag } from 'next/cache';
export default function Dashboard() {
return <div>Loading...</div>;
}
export async function generateMetadata() {
const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
const data = await res.json();
// Revalidate on the server when a specific tag changes
revalidateTag('data');
return { data };
}Client and Server Components
Next.js distinguishes between client and server components. Server components render on the server, while client components run on the client.
// app/dashboard/server-component.js (Server Component)
export default async function ServerComponent() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>Data from server: {json.text}</div>;
}
// app/dashboard/client-component.js (Client Component)
export default async function ClientComponent() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>Data from client: {json.text}</div>;
}Streaming and Suspense
Next.js supports streaming and Suspense, allowing loading states to be displayed during page loading.
// app/dashboard/page.js
export default async function Dashboard() {
const data = await fetch('https://api.example.com/data', { next: { revalidate: 60 } });
const json = await data.json();
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<div>Data: {json.text}</div>
</Suspense>
</div>
);
}Pages and Layouts
Basic Concepts
In Next.js 14, pages and layouts are organized in the app directory. Each page can have its own layout, and layouts can include additional layouts or pages.
- Page (page.js): Each page file represents a specific page.
- Layout (layout.js): Layout files define shared page elements, such as navigation bars and footers.
- Error Boundary (error.js): Captures and handles errors on the page.
- Loading State (loading.js): Displays the loading state during page load.
Creating a Basic Layout
Create a basic application layout.
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/dashboard">Dashboard</a>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Creating Pages
Create a simple homepage and dashboard page.
// app/page.js
export default function Home() {
return (
<div>
<h1>Welcome to My App</h1>
<p>This is the home page.</p>
</div>
);
}
// app/dashboard/page.js
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<p>This is the dashboard page.</p>
</div>
);
}Creating Nested Routes
Create nested layouts and pages to represent complex page structures.
// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
return (
<section>
<h2>Dashboard</h2>
{children}
</section>
);
}
// app/dashboard/settings/page.js
export default function Settings() {
return (
<div>
<h3>Settings</h3>
<p>This is the settings page.</p>
</div>
);
}Using a Loading Component
Define a default loading state with loading.js.
// app/dashboard/loading.js
export default function Loading() {
return <div>Loading...</div>;
}Error Handling
Error boundaries allow capturing and handling page errors.
// app/dashboard/error.js
export default function Error({ error, reset }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Passing Data to Pages
Use generateMetadata or generateStaticParams to pass data to pages.
// app/dashboard/settings/page.js
export default function Settings({ params }) {
return (
<div>
<h3>Settings</h3>
<p>This is the settings page for {params.id}.</p>
</div>
);
}
export async function generateStaticParams() {
return [{ id: 'profile' }, { id: 'security' }];
}Using Server Components
Server components render on the server, aiding SEO and performance optimization.
// app/dashboard/server-component.js
export default async function ServerComponent() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>Data from server: {json.text}</div>;
}Using Client Components
Client components render on the client.
// app/dashboard/client-component.js
export default async function ClientComponent() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>Data from client: {json.text}</div>;
}Using Suspense
Suspense handles asynchronous data loading, displaying a loading state until data is ready.
// app/dashboard/page.js
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading...</div>}>
<ServerComponent />
</Suspense>
</div>
);
}Route Grouping
Route groups define shared layouts or error-handling logic for related pages.
// app/dashboard/[...group].js
export default function Group({ params }) {
return <div>Group: {params.group.join('/')}</div>;
}
export async function generateStaticParams() {
return [
{ group: ['settings'] },
{ group: ['preferences'] },
];
}Route Handlers
Route handlers define API paths for specific routes.
// app/api/dashboard/route.js
export async function GET(request) {
return new Response(JSON.stringify({ message: 'Hello from the dashboard!' }));
}
export async function POST(request) {
const body = await request.json();
console.log(body);
return new Response(JSON.stringify({ message: 'Data received' }));
}Metadata
Add metadata like titles and descriptions to pages.
// app/dashboard/page.js
export default function Dashboard() {
return <div>Dashboard Page</div>;
}
export async function generateMetadata() {
return {
title: 'My Dashboard',
description: 'This is the dashboard page.',
};
}Redirects
Use the redirect function to redirect users to another page.
// app/dashboard/redirect.js
export default function RedirectPage() {
return <div>Redirecting...</div>;
}
export async function generateMetadata() {
redirect('/dashboard/settings');
}Links and Navigation
Links and Navigation
Next.js provides the <Link> component for creating links and the useRouter hook for programmatic navigation.
// app/layout.js
import Link from 'next/link';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<header>
<nav>
<Link href="/">Home</Link>
<Link href="/dashboard">Dashboard</Link>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Navigating with useRouter
Use the useRouter hook for programmatic navigation.
// app/dashboard/page.js
import { useRouter } from 'next/navigation';
export default function Dashboard() {
const router = useRouter();
return (
<div>
<h1>Dashboard</h1>
<button onClick={() => router.push('/dashboard/settings')}>
Go to Settings
</button>
</div>
);
}Route Groups
Route Groups
Route groups allow defining shared layouts or error-handling logic for related pages without affecting the URL path.
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<section>
<h1>Marketing Section</h1>
{children}
</section>
);
}
// app/(marketing)/campaigns/page.js
export default function Campaigns() {
return (
<div>
<h2>Campaigns</h2>
<p>This is the campaigns page.</p>
</div>
);
}Using Route Groups
Assume there are marketing and shop route groups.
// app/(marketing)/campaigns/page.js
export default function Campaigns() {
return (
<div>
<h2>Campaigns</h2>
<p>This is the campaigns page.</p>
</div>
);
}
// app/(shop)/products/page.js
export default function Products() {
return (
<div>
<h2>Products</h2>
<p>This is the products page.</p>
</div>
);
}Route Group Example
A more complete route group example:
// app/(marketing)/layout.js
export default function MarketingLayout({ children }) {
return (
<section>
<h1>Marketing Section</h1>
{children}
</section>
);
}
// app/(marketing)/campaigns/page.js
export default function Campaigns() {
return (
<div>
<h2>Campaigns</h2>
<p>This is the campaigns page.</p>
</div>
);
}
// app/(marketing)/ads/page.js
export default function Ads() {
return (
<div>
<h2>Ads</h2>
<p>This is the ads page.</p>
</div>
);
}
// app/(shop)/layout.js
export default function ShopLayout({ children }) {
return (
<section>
<h1>Shop Section</h1>
{children}
</section>
);
}
// app/(shop)/products/page.js
export default function Products() {
return (
<div>
<h2>Products</h2>
<p>This is the products page.</p>
</div>
);
}
// app/(shop)/checkout/page.js
export default function Checkout() {
return (
<div>
<h2>Checkout</h2>
<p>This is the checkout page.</p>
</div>
);
}Dynamic Routes
Dynamic routes in Next.js allow generating pages based on dynamic data, useful for handling URLs with variable parameters like /users/:userId or /posts/:postId.
Creating Dynamic Routes
// app/users/[userId]/page.js
export default function UserPage({ params }) {
return (
<div>
<h1>User ID: {params.userId}</h1>
<p>This is the user page for {params.userId}.</p>
</div>
);
}Generating Static Parameters
// app/users/[userId]/page.js
export async function generateStaticParams() {
return [
{ userId: '1' },
{ userId: '2' },
{ userId: '3' },
];
}Using Dynamic Routes
// app/layout.js
import Link from 'next/link';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<header>
<nav>
<Link href="/">Home</Link>
<Link href="/users/1">User 1</Link>
<Link href="/users/2">User 2</Link>
<Link href="/users/3">User 3</Link>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Project Organization and File Hosting
Effective project organization in Next.js is crucial for managing code efficiently. Below are some recommendations for project organization:
Directory Structure
/my-app
├── /app
│ ├── /components
│ │ ├── /common
│ │ │ └── Navbar.js
│ │ ├── /dashboard
│ │ │ └── Sidebar.js
│ │ └── /users
│ │ └── UserProfile.js
│ ├── /layouts
│ │ ├── RootLayout.js
│ │ └── DashboardLayout.js
│ ├── /pages
│ │ ├── page.js
│ │ ├── /dashboard
│ │ │ ├── page.js
│ │ │ └── /settings
│ │ │ └── page.js
│ │ └── /users
│ │ └── [userId]
│ │ └── page.js
│ ├── /utils
│ │ └── api.js
│ └── /styles
│ └── global.css
├── /public
└── .env.localComponent and Layout Separation
- Place common components in the
/components/commondirectory. - Store page-specific components in the corresponding page directory, e.g.,
/components/dashboard/Sidebar.js. - Keep layout files in the
/layoutsdirectory.
API and Utilities
- Place API request and data processing code in the
/utilsdirectory. - Store utility functions in the
/utilsdirectory.
Style Management
- Store global styles in
/styles/global.css. - Component-level styles can be included in the component file or separate
.cssfiles.
Environment Variables
- Use
.env.localto manage environment variables. - Avoid hardcoding sensitive information in the source code.
File Hosting
- Place static resource files in the
/publicdirectory. - Next.js automatically handles the deployment of these files.
Creating a Dynamic Route Page
// app/users/[userId]/page.js
export default function UserPage({ params }) {
return (
<div>
<h1>User ID: {params.userId}</h1>
<p>This is the user page for {params.userId}.</p>
</div>
);
}
export async function generateStaticParams() {
return [
{ userId: '1' },
{ userId: '2' },
{ userId: '3' },
];
}Using Dynamic Routes
// app/layout.js
import Link from 'next/link';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<header>
<nav>
<Link href="/">Home</Link>
<Link href="/users/1">User 1</Link>
<Link href="/users/2">User 2</Link>
<Link href="/users/3">User 3</Link>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Parallel Routes
Parallel routes, introduced in Next.js 14, allow loading multiple pages in different parts of the application simultaneously, without waiting for others to complete. They are implemented using parentheses () in the app directory.
Creating Parallel Routes
// app/(marketing)/campaigns/page.js
export default function Campaigns() {
return (
<div>
<h1>Campaigns</h1>
<p>This is the campaigns page.</p>
</div>
);
}
// app/(marketing)/ads/page.js
export default function Ads() {
return (
<div>
<h1>Ads</h1>
<p>This is the ads page.</p>
</div>
);
}Parallel Routes Example
// app/layout.js
import Link from 'next/link';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<header>
<nav>
<Link href="/">Home</Link>
<Link href="/(marketing)/campaigns">Campaigns</Link>
<Link href="/(marketing)/ads">Ads</Link>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Accessing Parallel Routes
// app/page.js
import Link from 'next/link';
export default function HomePage() {
return (
<div>
<h1>Welcome to My App</h1>
<p>This is the home page.</p>
<Link href="/(marketing)/campaigns">Go to Campaigns</Link>
<Link href="/(marketing)/ads">Go to Ads</Link>
</div>
);
}Intercepting Routes
Intercepting routes involve performing operations before a user accesses a page, such as verifying login status or redirecting. In Next.js, use useEffect or useRouter to implement route interception.
Intercepting Routes with useEffect
// app/(marketing)/campaigns/page.js
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function Campaigns() {
const router = useRouter();
useEffect(() => {
const isLoggedIn = false; // Assume this checks if the user is logged in
if (!isLoggedIn) {
router.push('/login');
}
}, []);
return (
<div>
<h1>Campaigns</h1>
<p>This is the campaigns page.</p>
</div>
);
}Intercepting Routes with useRouter
// app/(marketing)/ads/page.js
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function Ads() {
const router = useRouter();
useEffect(() => {
const hasAccess = true; // Assume this checks user access permissions
if (!hasAccess) {
router.push('/unauthorized');
}
}, []);
return (
<div>
<h1>Ads</h1>
<p>This is the ads page.</p>
</div>
);
}Route Handlers
Route handlers in Next.js allow defining functions to handle HTTP requests, supporting methods like GET, POST, PUT, DELETE, and returning JSON or other response types. They are typically located in the app/api directory.
Creating Route Handlers
// app/api/hello/route.js
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
return NextResponse.json({ message: 'Hello, World!' });
}
export async function POST(request: NextRequest) {
const body = await request.json();
console.log(body);
return NextResponse.json({ message: 'Data received', data: body });
}Accessing Route Handlers
Access the route handler by sending HTTP requests to /api/hello.
# Send a GET request using curl
curl http://localhost:3000/api/hello
# Send a POST request using curl
curl -X POST -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:3000/api/helloAPI Requests
Sending API Requests
Next.js 14 supports using the modern browser fetch function to send API requests.
// app/home/page.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>
);
}API Routes
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!' });
}Asynchronous Data Fetching with useEffect and useState
In client-rendered pages, use useEffect and useState for asynchronous data fetching.
// app/home/page.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>
);
}Fetching Data for Static Generation with generateStaticParams and load
For statically generated pages, use generateStaticParams and load to fetch data.
// app/home/page.js
export default function HomePage({ data }) {
return (
<div>
<h1>Welcome to Home Page</h1>
<p>Data: {data.message}</p>
</div>
);
}
export async function generateStaticParams() {
return [{ id: '1' }];
}
export async function load({ params }) {
const response = await fetch(`/api/data?id=${params.id}`);
const data = await response.json();
return { data };
}Summary
- Using fetch: Use the
fetchfunction in client-rendered pages to fetch data. - Using generateStaticParams and load: Use
generateStaticParamsandloadin statically generated pages to fetch data. - Using generateStaticParams and load: Use
generateStaticParamsandloadin server-rendered pages to fetch data.
Middleware
Middleware in Next.js intercepts and processes requests before they reach their final destination, useful for authentication, logging, or modifying requests/responses.
Creating Middleware
// middleware.js
import { NextRequest, NextResponse } from 'next/server';
export default function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value || '';
const path = request.nextUrl.pathname;
if (path === '/protected' && token !== 'secret-token') {
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: ['/protected'],
};Configuring Middleware
// next.config.js
module.exports = {
reactStrictMode: true,
swcMinify: true,
experimental: {
appDir: true,
},
async rewrites() {
return {
beforeFiles: [],
afterFiles: [],
fallback: [],
};
},
async redirects() {
return [];
},
async headers() {
return [];
},
async middleware(config) {
const { default: middleware } = await import('./middleware');
return [
middleware,
];
},
};Using Middleware
// app/protected/page.js
export default function ProtectedPage() {
return (
<div>
<h1>Protected Page</h1>
<p>This is a protected page. You should not see this if you are not logged in.</p>
</div>
);
}Internationalization
Next.js provides built-in internationalization support, enabling developers to add multi-language support with dynamic URLs, language preference settings, and translated text.
Setup Steps
Create next-i18next Configuration File
Create a configuration file, typically named next-i18next.config.js, to define supported languages and translation file locations.
// next-i18next.config.js
module.exports = {
i18n: {
locales: ['en', 'zh'], // Supported languages
defaultLocale: 'en', // Default language
localeDetection: false, // Disable automatic browser language detection
},
ns: ['common'], // Define namespaces
};Install Dependencies
Install next-i18next and i18next dependencies.
npm install next-i18next i18nextCreate Translation Files
Create translation folders in the public/locales directory, with a subfolder for each supported language containing translation files.
/public
├── /locales
│ ├── en
│ │ └── common.json
│ └── zh
│ └── common.jsonExample Translation Files:
// public/locales/en/common.json
{
"welcome": "Welcome",
"greeting": "Hello, {name}!"
}
// public/locales/zh/common.json
{
"welcome": "欢迎",
"greeting": "你好,{name}!"
}Configure Next.js
Update next.config.js to enable i18n support.
// next.config.js
const withI18next = require('next-i18next').default;
module.exports = withI18next({
i18n: {
locales: ['en', 'zh'],
defaultLocale: 'en',
localeDetection: false,
},
ns: ['common'],
});Using i18n
Use the useTranslation hook to access translated text in pages or components.
// app/page.js
import { useTranslation } from 'next-i18next';
export default function HomePage() {
const { t } = useTranslation('common'); // Use the 'common' namespace
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'World' })}</p>
</div>
);
}Setting Up Language Switching
Add language-switching links or buttons.
// app/layout.js
import { useTranslation, useLanguageQuery } from 'next-i18next';
export default function RootLayout({ children }) {
const { t } = useTranslation('common');
const { locale, locales } = useLanguageQuery();
return (
<html lang={locale}>
<head>
<title>{t('title')}</title>
</head>
<body>
<header>
<nav>
<ul>
{locales.map((l) => (
<li key={l}>
<a href={`/?lang=${l}`}>{l}</a>
</li>
))}
</ul>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Internationalization in Dynamic Routes
Support internationalization in dynamic routes using generateStaticParams.
// app/users/[userId]/page.js
import { useTranslation } from 'next-i18next';
export default function UserPage({ params }) {
const { t } = useTranslation('common');
return (
<div>
<h1>{t('userProfile', { userId: params.userId })}</h1>
<p>{t('userDetails')}</p>
</div>
);
}
export async function generateStaticParams() {
return [
{ userId: '1' },
{ userId: '2' },
{ userId: '3' },
];
}Example Code
Create next-i18next Configuration File
// next-i18next.config.js
module.exports = {
i18n: {
locales: ['en', 'zh'],
defaultLocale: 'en',
localeDetection: false,
},
ns: ['common'],
};Install Dependencies
npm install next-i18next i18nextCreate Translation Files
/public
├── /locales
│ ├── en
│ │ └── common.json
│ └── zh
│ └── common.json// public/locales/en/common.json
{
"welcome": "Welcome",
"greeting": "Hello, {name}!"
}
// public/locales/zh/common.json
{
"welcome": "欢迎",
"greeting": "你好,{name}!"
}Configure Next.js
// next.config.js
const withI18next = require('next-i18next').default;
module.exports = withI18next({
i18n: {
locales: ['en', 'zh'],
defaultLocale: 'en',
localeDetection: false,
},
ns: ['common'],
});Using i18n
// app/page.js
import { useTranslation } from 'next-i18next';
export default function HomePage() {
const { t } = useTranslation('common');
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'World' })}</p>
</div>
);
}Setting Up Language Switching
// app/layout.js
import { useTranslation, useLanguageQuery } from 'next-i18next';
export default function RootLayout({ children }) {
const { t } = useTranslation('common');
const { locale, locales } = useLanguageQuery();
return (
<html lang={locale}>
<head>
<title>{t('title')}</title>
</head>
<body>
<header>
<nav>
<ul>
{locales.map((l) => (
<li key={l}>
<a href={`/?lang=${l}`}>{l}</a>
</li>
))}
</ul>
</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}Internationalization in Dynamic Routes
// app/users/[userId]/page.js
import { useTranslation } from 'next-i18next';
export default function UserPage({ params }) {
const { t } = useTranslation('common');
return (
<div>
<h1>{t('userProfile', { userId: params.userId })}</h1>
<p>{t('userDetails')}</p>
</div>
);
}
export async function generateStaticParams() {
return [
{ userId: '1' },
{ userId: '2' },
{ userId: '3' },
];
}



