Lesson 17-Next.js Core Module Source Code Analysis

Implementation of next-server and next-app

next-server

next-server is the core server-side module of Next.js, primarily responsible for the following tasks:

  • Handling HTTP requests and responses.
  • Performing Server-Side Rendering (SSR).
  • Serving static assets.
  • Processing API routes.

Key Components of next-server:

  • Server: Handles HTTP requests.
  • Render: Executes server-side rendering.
  • Webpack: Manages Webpack configuration and build processes.
  • Middleware: Processes middleware logic, such as API routes.

next-app

The next-app module focuses on client-side application logic, including:

  • Client-Side Rendering (CSR).
  • Client-side route navigation.
  • State and lifecycle management.

Key Components of next-app:

  • App: The top-level application component.
  • Document: Used for rendering the HTML document.
  • Router: Manages client-side routing.
  • Redux: State management (if used).

Server-Side Rendering (SSR)

// server.js
const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.get('*', (req, res) => {
    return handle(req, res);
  });

  server.listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

Client-Side Routing

// pages/_app.js
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  return <Component {...pageProps} />;
}

export default MyApp;

How the Routing System Works

Next.js Routing Types

Next.js has two routing systems:

  • Pages Router: The original routing system based on the file system to define page routes.
  • App Router: A new routing system introduced in Next.js 13, offering advanced features and improved performance.

Pages Router Mechanics

In the Pages Router, page routes are automatically generated based on the file structure in the /pages directory. For example, a file named pages/about.js corresponds to the /about route.

  • Dynamic Routes: If a filename includes square brackets [ ], it is treated as a dynamic route. For example, pages/post/[id].js corresponds to the /post/:id path.
  • File System Routing: Next.js automatically generates routes based on file names and folder structures.

App Router Mechanics

The App Router, introduced in Next.js 13, provides advanced features like shared layouts and streaming SSR. Its mechanics include:

  • File System Routing: Routes are still defined based on the file system but use a new folder structure, such as /app and /pages.
  • Shared Layouts: Uses layout.js files to define reusable layouts, which can be nested.
  • Streaming SSR: Progressively renders pages to improve First Contentful Paint (FCP).
  • Data Fetching: Supports fetch and use syntax for data retrieval.

Implementation of Pre-rendering and SSR

Next.js provides two primary methods for handling initial page rendering: Pre-rendering and Server-Side Rendering (SSR). Both aim to enhance application performance and user experience.

Pre-rendering generates static HTML pages at build time, aiding Search Engine Optimization (SEO) and improving initial load speed.

Implementation Mechanics

  • Static Generation: Generates static HTML files during the build process, which can be served directly to users after deployment.
  • Incremental Static Regeneration (ISR): Allows periodic or manual regeneration of static HTML files post-deployment.

Call Flow of getStaticProps and getServerSideProps

getStaticProps Call Flow

  • Build-Time Invocation: During the build process, Next.js iterates through all pages requiring static generation and invokes the getStaticProps function.
  • Data Fetching: The getStaticProps function retrieves data from external sources (e.g., APIs) and returns it as page props.
  • HTML Generation: Next.js uses the returned data to generate static HTML files, storing them in the .next folder.
// pages/post/[id].js
export default function PostPage({ post }) {
  return (
    <div>
      <h1>Post ID: {post.id}</h1>
      <p>Title: {post.title}</p>
      <p>Content: {post.content}</p>
    </div>
  );
}

export async function getStaticProps(context) {
  const { params } = context;
  const id = params.id;

  // Simulate fetching data from a database
  const response = await fetch(`https://example.com/api/posts/${id}`);
  const post = await response.json();

  return {
    props: {
      post,
    },
  };
}

export async function getStaticPaths() {
  // Simulate fetching all post IDs from a database
  const response = await fetch('https://example.com/api/posts');
  const posts = await response.json();

  const paths = posts.map((post) => ({
    params: { id: post.id.toString() },
  }));

  return {
    paths,
    fallback: false, // Can be set to 'blocking' or 'unstable_blocking' for dynamic routes
  };
}

Server-Side Rendering (SSR)

SSR involves generating HTML pages on the server in response to each request, ensuring the latest content is delivered.

Implementation Mechanics

  • Dynamic Generation: Generates HTML content on the server for each request.
  • Data Fetching: Typically fetches data on the server to ensure freshness.

getServerSideProps Call Flow

  • Request Arrival: When a user visits an SSR page, the request reaches the server.
  • Function Invocation: The server calls the getServerSideProps function to fetch data.
  • HTML Generation: Uses the fetched data to generate HTML and sends it to the client.
// pages/post/[id].js
export default function PostPage({ post }) {
  return (
    <div>
      <h1>Post ID: {post.id}</h1>
      <p>Title: {post.title}</p>
      <p>Content: {post.content}</p>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { params } = context;
  const id = params.id;

  // Simulate fetching data from a database
  const response = await fetch(`https://example.com/api/posts/${id}`);
  const post = await response.json();

  return {
    props: {
      post,
    },
  };
}

Route Resolution and Page Rendering

Route Resolution

Next.js supports two routing systems:

  • Pages Router: The original routing system, defining page routes based on the file system.
  • App Router: A new system introduced in Next.js 13, offering advanced features and improved performance.

Pages Router Mechanics

In the Pages Router, page routes are automatically generated based on the file structure in the /pages directory. For example, a file named pages/about.js corresponds to the /about route.

  • Dynamic Routes: If a filename includes square brackets [ ], it is treated as a dynamic route. For example, pages/post/[id].js corresponds to the /post/:id path.
  • File System Routing: Next.js generates routes based on file names and folder structures.

App Router Mechanics

The App Router, introduced in Next.js 13, provides advanced features like shared layouts and streaming SSR. Its mechanics include:

  • File System Routing: Routes are defined based on the file system, using a new folder structure like /app and /pages.
  • Shared Layouts: Uses layout.js files to define reusable, nestable layouts.
  • Streaming SSR: Progressively renders pages to improve FCP.
  • Data Fetching: Supports fetch and use syntax for data retrieval.

Page Rendering

Next.js offers multiple methods for handling initial page rendering, including Pre-rendering and SSR.

Pre-rendering

  • Static Generation: Generates static HTML files at build time, served directly to users post-deployment.
  • Incremental Static Regeneration (ISR): Allows periodic or manual regeneration of static HTML files after deployment.

Server-Side Rendering (SSR)

  • Dynamic Generation: Generates HTML content on the server for each request.
  • Data Fetching: Fetches data on the server to ensure content freshness.

API Route Handling

Next.js allows defining API routes in the /pages/api directory, primarily used to handle client-side API requests.

API Route Processing Flow

  • Request Arrival: The client sends an API request to the server.
  • Route Matching: The server matches the request URL to the corresponding API route.
  • Request Processing: Invokes the corresponding API function to process the request.
  • Response Delivery: Returns the processed result to the client.
// pages/api/posts.js
export default async function handler(req, res) {
  if (req.method === 'GET') {
    // Fetch data from external API
    try {
      const response = await fetch('https://example.com/api/posts');
      const result = await response.json();
      res.status(200).json(result);
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Failed to fetch posts' });
    }
  }
}
Share your love