React: Getting Started with Next.js

Last updated: 2026-08-26

Tom built a blog site using create-react-app. After launching it, he found that the first-screen load took 3 seconds, search engines couldn’t crawl the content, and he had to manually refresh the CDN cached every time he updated a post. He realized that cliente-side rendering alone wasn’t enough—he needed a framework capable of servidor-side rendering, static generation, and automatic optimization. That’s when Next.js came into the picture.

Next.js, developed and maintained by Vercel, is currently the most popular full-stack React framework. It addresses core issues that React, as a pure cliente-side UI library, cannot handle: routing systems, servidor-side rendering, static site generation, API routing, image optimization, font optimization, middleware, and more. Whether you’re building a blog, an e-commerce site, a SaaS application, or an enterprise-level application, Next.js provides best practices right out of the box.

This lesson begins by clarifying the core concepts and architectural design of Next.js. You’ll learn the differences between the four rendering modes, how the App Router works, and the division of labor between servidor-side and cliente-side components. These foundational concepts serve as the basis for further study of data fetching, deployment, and comprehensive hands-on project work. Through Tom’s real-world migration case study, you’ll gain a clear understanding of the complete technical migration path from CRA to Next.js.


1. What You'll Learn



2. Conceptual Diagrams

While learning Next.js, Tom drew a flowchart to understand the requisição processing path. When a user requisição arrives, Next.js selects a different rendering path based on the page’s configuration (static, dynamic, or incremental), ultimately returning HTML and enabling interaction on the browser side.

This diagram illustrates four key decision points: First, determine the page type (static/dynamic/incremental/cliente-side); then, select the corresponding rendering engine; generate HTML and return it to the browser; and finally, use the hydration process to enable cliente components on the page to become interactive.

100%
flowchart LR
    A[User Request] --> B{Next.js Route Matching}
    B -->|Static Page| C[SSG<br/>Generated during build]
    B -->|Dynamic Pages| D[SSR<br/>Render on Request]
    B -->|Incremental Update| E[ISR<br/>Re-verify on Demand]
    B -->|Client Interaction| F[CSR<br/>Browser Execution]
    C --> G[Back HTML]
    D --> G
    E --> G
    F --> G
    G --> H[Hydration<br/>Activate Interaction]
    H --> I[Client Component<br/>Run JS]


3. A Real-Life Scenario

Tom’s blog was originally developed using create-react-app, with all pages rendered on the client side. When users opened an article, they first downloaded an empty HTML template, then waited for the JavaScript to finish loading before fetching data from the API to render the content. This was a disaster for a content-driven website—search engine crawlers only saw blank pages, and the time to first content (TFC) was as long as 3 seconds.

After conducting his research, he found that a "hybrid rendering" approach was needed: blog posts would be statically generated (HTML generated during the build process and distributed via a CDN), user profile pages would be server-side rendered (with the latest data generated for each request), and the comments section would be client-side rendered (with interactions handled in the browser). Next.js happens to provide all of this.

He spent a week migrating his blog from CRA to Next.js, doing three specific things: First, he switched the post pages to SSG, using generateStaticParams to generate HTML for all post pages during the build process; after deployment to Vercel, the CDN serves the pre-rendered pages directly, reducing the time to first content (TFC) to 0.3 seconds. Second, he switched the user profile pages to SSR, fetching the latest data from the database with every request; Third, the comments section was implemented using client-side components, loading only the interactive JavaScript on the browser side. After the migration, the site’s SEO score on Google Lighthouse improved from 45 to 98, and the time to first content (TFC) was reduced by 90%.

Tom also ran into quite a few pitfalls during the migration. For example, he initially used the Pages Router in the pages/ directory, but later found that the App Router worked better, so he spent extra time migrating to it. He recommends using App Router directly for new projects to avoid having to migrate a second time. He also discovered that useRouter and usePathname cannot be used directly in Server Components; instead, this routing logic needs to be extracted to Client Components. These lessons have given him a deep understanding of Next.js’s design philosophy—“server-first, client-on-demand.”

(1) Comparison of Rendering Modes

Next.js offers four rendering modes, each designed for different use cases. Understanding the differences between them is key to choosing the right architecture.

CSR (Client-Side Rendering): The browser downloads an empty HTML shell and then executes JavaScript to render the content. Tom’s blog originally used this model. The advantages are smooth interactivity and reduced load on the server; the disadvantages are slow first-screen loading and poor SEO. It is suitable for interactive applications that require user login, such as dashboards and administrative backends.

SSR (Server-Side Rendering): Each time a user makes a request, the server dynamically generates and returns the complete HTML. Tom used this approach to solve his blog’s SEO issues, but since each request must wait for the server to finish rendering before a response is returned, it places a heavy load on the server. It is suitable for pages that require real-time data, such as news sites and e-commerce product pages.

SSG (Static Site Generation): Generates HTML files for all pages during the build phase; after deployment to a CDN, users access the static files directly. This offers the fastest loading speed and the best SEO, but content updates require a full rebuild. Tom’s blog post pages are a good example of this model—once an article is written, its content remains fixed. Suitable for blogs, documentation sites, and marketing pages.

ISR (Incremental Static Generation): An enhanced version of SSG that allows specific pages to be revalidated and updated on demand after the build, without requiring a full site rebuild. For example, if Tom’s product catalog page is set to revalidate every 60 seconds, new products will appear within 60 seconds of being added. This is ideal for e-commerce product pages and sites with frequently updated content.

The selection strategy can be summarized as follows: use SSG for content-first scenarios, SSR for real-time data, CSR for interaction-intensive scenarios, and ISR for scenarios with frequent updates where you don’t want to rebuild the entire site.

Rendering Mode Performance Comparison Table

Indicator SSG ISR SSR CSR
First-Screen Load Speed Fastest Fast Average Slow
SEO Friendliness High High High Low
Data Timeliness Fixed at build time Updated on demand Latest data with each request After the browser loads
Server Load None Low High Low
Use Cases Blogs/Documentation E-commerce/News Custom Pages Backend Management
Typical TTFB <50ms <100ms 200-500ms 200-500ms

▶ Example 1: Implementing the Four Rendering Modes in Next.js

Output:

TEXT 📖 Display only
URL routing: / → Home, /about → About, /contact → Contact. Navigation without page reload.

The code below demonstrates the implementation of the three rendering modes in Tom's blog. Note that generateStaticParams is called during the build process and returns all possible parameter combinations; Next.js uses this to generate static HTML for all pages. dynamic = 'force-dynamic', on the other hand, forces the page to be re-rendered with every request.

TSX
// === Static Generation SSG(Default behavior) ===
// app/blog/[slug]/page.tsx
// Automatically scan all articles during the build process slug,Generate the corresponding HTML Page
// After generation CDN Return static files directly,No server-side rendering required

export async function generateStaticParams() {
  // Called during build:Get a list of all articles
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())
  // Back slug Array,Next.js It will do this for each slug Generate a page
  return posts.map((post: any) => ({ slug: post.slug }))
}

async function BlogPost({ params }: { params: { slug: string } }) {
  // Each page retrieves data independently when it is rendered.
  const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json())
  return (
    <article>
      <h1>{post.title}</h1>
      <time>{post.publishedAt}</time>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}
export default BlogPost

// === Server-Side Rendering SSR(Dynamic Rendering) ===
// app/dashboard/page.tsx
// Every user request starts from API Get the latest data,Ensure the data is up to date

export const dynamic = 'force-dynamic' // Force the static cache to close,Leaving every time SSR

async function DashboardPage() {
  // This will be executed with every request fetch
  const stats = await fetch('https://api.example.com/dashboard/stats').then(r => r.json())
  return (
    <div>
      <h1>Real-Time Dashboard</h1>
      <p>Current Online Users:{stats.onlineUsers}</p>
      <p>Today's Orders:{stats.todayOrders}</p>
      <p>Income for This Month:${stats.monthlyRevenue}</p>
    </div>
  )
}
export default DashboardPage

// === Incremental Static Generation ISR ===
// app/products/[id]/page.tsx
// Generate static pages during the build process,But every 60 The first visit after X seconds will trigger a regenerate.

async function ProductPage({ params }: { params: { id: string } }) {
  const product = await fetch(`https://api.example.com/products/${params.id}`, {
    next: { revalidate: 60 } // ISR:60 Please try again in a few seconds.
  }).then(r => r.json())
  return (
    <div>
      <h1>{product.name}</h1>
      <p>Price:${product.price}</p>
      <p>Inventory:{product.stock}</p>
      <p>Description:{product.description}</p>
    </div>
  )
}
export default ProductPage

In his blog, Tom opted for a hybrid approach combining SSG and ISR: he uses SSG for article pages to ensure the homepage loads quickly, and ISR for product list pages, which updates prices and inventory every 60 seconds. This approach ensures good SEO scores while also maintaining data timeliness.

(2) App Router File Routing

Next.js 13+ introduces the App Router, which automatically generates routes based on the file system. All you need to do is create a folder named app/ and a file named page.tsx within that folder, and Next.js will automatically map the corresponding URL paths. You no longer need to manually maintain route configuration files—the directory structure is the route structure.

Core File Types

Dynamic Routing and Advanced Modes

Use the [param] syntax to create dynamic routes; for example, app/blog/[slug]/page.tsx matches /blog/hello-world. Use the catch-all syntax [...param] to match multi-level paths; for example, app/docs/[...slug]/page.tsx matches /docs/guide/getting-started/installation.

Layout files support deeply nested layouts—where a parent Layout wraps the content of child pages. Common sections such as the navigation bar, sidebar, and footer only need to be written once; Next.js automatically manages the persistent state of the Layout. When switching pages, the Layout is not re-rendered; only the child pages are updated.

▶ Example 2: Complete Blog Routing Structure

Output:

TEXT 📖 Display only
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
Command executed
│   ├── layout.tsx          # → Dashboard-Exclusive Layout(With sidebar navigation)
│   └── settings/
│       └── page.tsx        # → /dashboard/settings Settings Page
└── api/
    └── hello/
        └── route.ts        # → /api/hello API Endpoint

Output:

TEXT 📖 Display only
Renders: RootLayout component UI

Route Mapping Reference Table

File Path Corresponding URL Description
app/page.tsx / Home
app/about/page.tsx /about Static Routes
app/blog/[slug]/page.tsx /blog/:slug Dynamic Routing
app/blog/[slug]/loading.tsx /blog/:slug Loading states on the same route
app/dashboard/layout.tsx /dashboard/* Nested Layout
app/api/hello/route.ts /api/hello API endpoint
TSX
// app/layout.tsx - Global Layout
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>
        <header>
          <nav>
            <a href="/">Home</a>
            <a href="/blog">Blog</a>
            <a href="/about">About</a>
          </nav>
        </header>
        <main>{children}</main>
        <footer>&copy; 2026 Tom 's blog. All rights reserved.</footer>
      </body>
    </html>
  )
}

// app/dashboard/layout.tsx - Dashboard-Exclusive Layout(Nested within the global layout)
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: 240, background: '#f5f5f5', padding: 16 }}>
        <nav>
          <a href="/dashboard">Overview</a>
          <a href="/dashboard/settings">Settings</a>
        </nav>
      </aside>
      <section style={{ flex: 1, padding: 16 }}>{children}</section>
    </div>
  )
}

// app/blog/[slug]/loading.tsx - Article Details Loading...
export default function Loading() {
  return (
    <div style={{ padding: 24 }}>
      <div style={{ height: 32, width: '60%', background: '#eee', borderRadius: 4, marginBottom: 16 }} />
      <div style={{ height: 16, width: '30%', background: '#eee', borderRadius: 4, marginBottom: 24 }} />
      <div style={{ height: 200, background: '#eee', borderRadius: 4 }} />
    </div>
  )
}

(3) Server Component and Client Component

One of Next.js’s revolutionary designs is the Server Component. This marks the first time in the React ecosystem that components have been divided into two execution environments: server-side and client-side. By default, all components in the App Router are Server Components—they run on the server, can directly access the database, the file system, and sensitive environment variables, and, most importantly, do not send any JavaScript to the browser.

When you need interactive functionality (such as clicking a button, entering text, using useState or useEffect, or listening for window events), a server-side component alone isn’t enough. In this case, you need to add the 'use client' declaration at the top of the file. Next.js will then mark that component and all its child components as Client Components and bundle them to be executed on the browser side.

Features of the Server Component

Features of the Client Component

Best Practice Models

It adopts a layered architecture where the "Server Component" acts as the container and the "Client Component" acts as the leaf. The outer-layer Server Component is responsible for fetching data and handling layout, while the inner-layer Client Component is responsible only for widgets that require interaction. In this way, most of the logic and data retrieval are handled on the server side, and the browser runs only the minimum necessary JavaScript.

▶ Example 3: Optimal Division of Responsibilities Between Server and Client Components

Output:

TEXT 📖 Display only
Parent layout with <Outlet />. /users → UserList, /users/:id → UserProfile. Nested routes share parent layout.

The code below illustrates the layered model described in Tom’s blog: “Data retrieval on the server, interactions in the browser.” PostsPage is a Server Component—it fetches data on the server, renders it as HTML, and sends it directly to the browser. Users see the complete page without having to wait for JavaScript to load. PostList is a Client Component—it receives data prepared by the server and is responsible only for search, filtering, and deletion interactions on the browser side. Note that 'use client' is only applied to “leaf components” that require interaction, not to the entire page.

TSX
// app/posts/page.tsx - Server Component(Default)
// This component runs on the server.,Do not send any JS Go to the browser
import PostList from './PostList'

async function PostsPage() {
  // Directly on the server fetch,Data is being compiled HTML It was already ready at that time
  // Users see the full page,None loading Status
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())

  return (
    <div>
      <h1>All Articles</h1>
      <PostList posts={posts} />
    </div>
  )
}
export default PostsPage

// app/posts/PostList.tsx - Annotation 'use client' Become Client Component
// Only this file will be sent JS Go to the browser,The parent component that contains it(Server Component)No
'use client'
import { useState } from 'react'

interface Post {
  id: number
  title: string
  body: string
}

function PostList({ posts: initialPosts }: { posts: Post[] }) {
  // useState Only at 'use client' Available in the component
  const [posts, setPosts] = useState(initialPosts)
  const [search, setSearch] = useState('')

  const filtered = posts.filter(p =>
    p.title.toLowerCase().includes(search.toLowerCase())
  )

  function handleDelete(id: number) {
    setPosts(prev => prev.filter(p => p.id !== id))
  }

  return (
    <div>
      <input
        type="text"
        placeholder="Search Articles..."
        value={search}
        onChange={e => setSearch(e.target.value)}
        style={{ width: '100%', padding: 8, marginBottom: 16, border: '1px solid #ddd', borderRadius: 4 }}
      />
      {filtered.map(post => (
        <div key={post.id} style={{
          border: '1px solid #ddd', padding: 16, marginBottom: 8, borderRadius: 8,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center'
        }}>
          <div>
            <h2 style={{ margin: 0 }}>{post.title}</h2>
            <p style={{ margin: '8px 0', color: '#666' }}>{post.body}</p>
          </div>
          <button
            onClick={() => handleDelete(post.id)}
            style={{ padding: '4px 12px', background: '#ff4444', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}
          >
            Delete
          </button>
        </div>
      ))}
      {filtered.length === 0 && <p>No matching articles were found.</p>}
    </div>
  )
}
export default PostList

// pages/old-posts.js - Pages Router Pattern(Compatibility with Legacy Projects)
// If you are maintaining Next.js 12 or earlier projects,Pages Router Usage getStaticProps/getServerSideProps
export async function getStaticProps() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())
  return { props: { posts }, revalidate: 60 }
}

export default function OldPostsPage({ posts }: { posts: Post[] }) {
  return (
    <div>
      <h1>Pages Router Pattern</h1>
      {posts.map(p => <p key={p.id}>{p.title}</p>)}
    </div>
  )
}

Server Component vs Client Component restrictions

Capability Server Component Client Component
Use useState/useEffect
Use onClick/onChange
Direct database access
Access environment variables (without prefix)
Send JS to the browser
Use async/await
Use useRouter/usePathname
Use requestAnimationFrame

Flowchart: Server Component or Client Component?

TEXT 📖 Display only
Components require interaction(Click、Input、Scrolling, etc.)?
├── is  → Required useState/useEffect?
│   ├── Yes → add 'use client' → Client Component
│   └── No → only accept props for rendering?
│       ├── is  → Retain Server Component
│       └── No → add 'use client'
└── No → keep Server Component


4. Tom's Migration Roadmap

We’ve now covered all the core concepts of Next.js. Next, let’s put this knowledge together and see how Tom migrated his CRA blog to Next.js step by step. This process also serves as the standard approach for adopting Next.js in your own projects.

Tom breaks down the complete process of migrating a blog from CRA to Next.js into five steps, each of which corresponds to a key concept covered in this lesson:


▶ Example 4: App Router loading.tsx and error.tsx

Output:

TEXT 📖 Display only
useEffect manages side effects. React Router handles navigation. Async data fetching/loading states
TSX
// app/dashboard/loading.tsx - Automatically Displayed Loading Status
export default function DashboardLoading() {
  return (
    <div style={{ padding: 24 }}>
      <div style={{ height: 32, width: 200, background: '#f0f0f0', borderRadius: 4, marginBottom: 16 }} />
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
        {[1, 2, 3].map(i => (
          <div key={i} style={{
            height: 120, background: 'linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)',
            backgroundSize: '200% 100%', borderRadius: 8, animation: 'shimmer 1.5s infinite',
          }} />
        ))}
      </div>
    </div>
  )
}

// app/dashboard/error.tsx - Automatically Displayed Error Status
'use client'
export default function DashboardError({ error, reset }) {
  return (
    <div style={{ padding: 40, textAlign: 'center' }}>
      <h2>Something went wrong!</h2>
      <p style={{ color: '#999' }}>{error.message}</p>
      <button onClick={reset} style={{ padding: '8px 24px', cursor: 'pointer' }}>Try again</button>
    </div>
  )
}

// app/dashboard/page.tsx - It will automatically use the one above loading and  error
async function DashboardPage() {
  const data = await fetch('https://api.example.com/dashboard').then(r => r.json())
  return (
    <div>
      <h1>Dashboard</h1>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16 }}>
        {data.stats.map(stat => (
          <div key={stat.label} style={{ padding: 16, border: '1px solid #eee', borderRadius: 8 }}>
            <p style={{ color: '#999', margin: 0 }}>{stat.label}</p>
            <p style={{ fontSize: 24, fontWeight: 'bold', margin: '4px 0 0' }}>{stat.value}</p>
          </div>
        ))}
      </div>
    </div>
  )
}
export default DashboardPage

Output:

TEXT 📖 Display only
Infinite loop: setState in useEffect without deps → render → effect → setState → render... Fix: add deps or use empty array [].

▶ Example 5: Nested Layouts and Templates

Output:

TEXT 📖 Display only
Heading: "Dashboard". Button: Try again. Async data fetching/loading states
TSX
// app/layout.tsx - Root Layout(Must have html and  body)
export default function RootLayout({ children }) {
  return (
    <html lang="zh">
      <body style={{ margin: 0, fontFamily: 'sans-serif' }}>
        {children}
      </body>
    </html>
  )
}

// app/(dashboard)/layout.tsx - Dashboard Layout(Route groups have no effect URL)
import Sidebar from '@/components/Sidebar'

export default function DashboardLayout({ children }) {
  return (
    <div style={{ display: 'flex', minHeight: '100vh' }}>
      <Sidebar />
      <main style={{ flex: 1, padding: 24 }}>
        {children}
      </main>
    </div>
  )
}

// app/(auth)/layout.tsx - Authentication Page Layout(Centered Card)
export default function AuthLayout({ children }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#f5f5f5' }}>
      <div style={{ background: 'white', padding: 32, borderRadius: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.1)', width: 400 }}>
        {children}
      </div>
    </div>
  )
}

// app/(auth)/login/page.tsx - Login Page
function LoginPage() {
  return (
    <>
      <h2 style={{ textAlign: 'center' }}>Sign In</h2>
      <form style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <input type="email" placeholder="Email" style={{ padding: 8, borderRadius: 4 }} />
        <input type="password" placeholder="Password" style={{ padding: 8, borderRadius: 4 }} />
        <button type="submit" style={{ padding: 10, background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
          Sign In
        </button>
      </form>
    </>
  )
}
export default LoginPage

Output:

TEXT 📖 Display only
URL routing: / → Home, /about → About, /contact → Contact. Navigation without page reload.

❓ FAQ

Q What is the relationship between Next.js and React?
A React is a UI library responsible for component rendering and state management; Next.js is a full-stack framework built on top of React, with built-in features such as routing, SSR/SSG/ISR, API routing, image optimization, font optimization, and middleware. You can think of it this way: React provides the building blocks, while Next.js provides the complete toolchain for assembling those building blocks into a production-ready application. React focuses on “how to render the UI,” while Next.js addresses “how to build a complete web application.”
Q How do I choose between Server Components and Client Components?
A Prioritize using Server Components (the default). Only add 'use client' when a component needs to use useState, useEffect, onClick, or browser APIs. A common pattern is for a Server Component to fetch data and pass it to an internal Client Component to handle interactions. This way, data retrieval is handled on the server, while interaction logic runs in the browser—the best of both worlds. A common mistake among beginners is adding 'use client' to the entire page—it should only be applied to the smallest interactive leaf nodes. If you find a component that requires neither interaction nor state, do not add 'use client'.
Q Which has better performance, SSR or SSG?
A SSG offers better performance because the HTML is generated during the build process, and the CDN directly serves static files without any server-side computation overhead. However, SSG is not suitable for pages with frequently changing content. SSR re-renders with every request, resulting in higher latency, but the data is always up-to-date. A compromise is ISR—it’s as fast as SSG most of the time and regenerates content on demand when updates are needed. In real-world projects, a hybrid approach is typically used: SSG for marketing pages, ISR for product pages, and SSR for the user dashboard.
Q What is the difference between the App Router and the Pages Router?
A The Pages Router is the routing method used in Next.js 12 and earlier versions. It uses the pages/ directory and maps routes based on filenames. App Router is the new routing system introduced in Next.js 13. It uses the app/ directory and supports new features such as nested layouts, server components, and streaming rendering. For new projects, it is recommended to use App Router directly; existing projects can be migrated gradually. The getStaticProps/getServerSideProps structure in Pages Router has been replaced by async component + fetch in App Router. The two routing systems can coexist, and during migration, you can gradually move pages from Pages Router to App Router.
Q Do Next.js projects have to be deployed to Vercel?
A Not necessarily. Although Vercel is the creator of Next.js and offers the most seamless deployment experience (one-click deployment, automatic scaling, and edge network), Next.js can also be deployed to other platforms. You can use next build && next start to deploy to any Node.js server, or use Docker for containerized deployment. Netlify, AWS Amplify, and Cloudflare Pages also support Next.js deployment. However, if you’re using advanced features such as ISR and middleware, Vercel offers the best compatibility.
Q What if I start with Pages Router and later want to migrate to App Router?
A The two routing systems can coexist in the same project. You can gradually move the pages from pages/ to app/, verifying each one as you move it—there’s no need to migrate everything at once. During migration, note the following: Change getStaticProps to fetch directly within the Server Component; move the Layout to layout.tsx, created under the app/ directory; and migrate the logic from pages/_app.tsx to the root layout.tsx. We recommend starting with the simplest pages and tackling more complex ones once you’ve gained some experience.

📖 Summary


📝 Exercises

  1. Use npx create-next-app@latest my-blog to create a new project, ensuring that the App Router directory structure is used. Under app/, create three page routes: /about, /blog, and /blog/[slug]. Each page should contain at least one title and a description. Run npm run dev and visit each route (/about, /blog, /blog/test-article) to verify that the mappings are correct.

  2. Implement a global layout (app/layout.tsx) that includes a top navigation bar (with three links: Home, Blog, and About) and a footer. Next, create a nested layout for the /blog/* route to display the article table of contents on the left side of the article detail page. Open React DevTools in your browser to verify that the layout’s nesting hierarchy is correct and that the navigation bar and sidebar retain their state without re-rendering when switching pages.

  3. Change the blog homepage to a Server Component that retrieves the list of articles directly from the JSONPlaceholder API (https://jsonplaceholder.typicode.com/posts) and renders them. Then, create a Client Component to implement keyword search and filtering functionality, allowing users to search and filter articles in real time on the browser side. Ensure that the Server Component handles data retrieval, while the Client Component handles only the interactive filtering logic. Once this is complete, add a loading.tsx (to display a placeholder screen) and a error.tsx (to display error messages and a retry button) to experience the full routing segment configuration.

  4. Optional: Deploy your blog to Vercel (free), and observe the ISR revalidation process in the Vercel console—after modifying the data returned by the API, check whether the page automatically updates after the revalidate time expires.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏