惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
宝玉的分享
宝玉的分享
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
D
Docker
B
Blog
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
G
Google Developers Blog
博客园 - Franky
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42

MDN Blog

Introducing the MDN MCP server | MDN Blog Under the hood of MDN's new frontend | MDN Blog Image formats: Codecs and compression tools | MDN Blog A beginner-friendly guide to view transitions in CSS | MDN Blog Launching MDN's new front end | MDN Blog Image formats: Pixel data from encoders to decoders | MDN Blog Celebrating 20 years of MDN | MDN Blog Image formats: Color models for humans and devices | MDN Blog Default styles for h1 elements are changing | MDN Blog Implications of Global Privacy Control | MDN Blog JavaScript Temporal is coming | MDN Blog Fix your website's Largest Contentful Paint by optimizing image loading | MDN Blog MDN 2024 content projects | MDN Blog A new learning experience on MDN | MDN Blog Countdown to the holidays with daily coding challenges | MDN Blog Monitoring and optimizing website performance | MDN Blog How to land your first developer job | MDN Blog Introducing the new MDN Community page | MDN Blog Fixing your website's JavaScript performance | MDN Blog Get back to school! Supercharge your learning with MDN and Scrimba | MDN Blog Efficient data handling with the Streams API | MDN Blog Locale-sensitive text segmentation in JavaScript with Intl.Segmenter | MDN Blog Optimize your workflow with Git stash | MDN Blog How to debug mobile apps across devices | MDN Blog Exclusive accordions using the HTML details element | MDN Blog Exploring the Broadcast Channel API for cross-tab communication | MDN Blog MDN partners with Scrimba to enhance web development learning | MDN Blog Introducing the MDN HTTP Observatory | MDN Blog New JavaScript Set methods | MDN Blog Securing APIs: Express rate limit and slow down | MDN Blog
Static Site Generation (SSG) with Next.js | MDN Blog
Vultr June 28, 2024 7 minutes read · 2024-06-28 · via MDN Blog

Static Site Generators (SSGs) are tools for producing HTML in advance so a server can efficiently send the same content to all visitors without creating it first. Unlike dynamic web pages, where a server may query a database on page load and populate templates, an SSG pre-builds the files so that when you deploy them, the server has less work to do when your website is visited.

SSGs have several benefits, such as faster loading time and better visitor user experience. For developers, scalability is also convenient with static sites as you can cache and serve entire websites in Content Delivery Networks (CDNs) instead of deploying more servers. With no server-side processing or database interactions, static sites are more resilient to security vulnerabilities like SQL injection or cross-site scripting (XSS) attacks.

In this article, we will create a Next.js application and use the default rendering mode to create a static site that is performant and efficient.

Setting up a Next.js app on Vultr

To begin, deploy a server using the NodeJS image by following the steps in Deploying a server on Vultr section in our previous article. Next, let's proceed to access the server terminal via SSH and set up a project for our web application.

To get us started, we'll use create-next-app, a starter template for quickly bootstrapping Next.js applications. For illustration, we'll use static generation in our app but fetch some data at build time. This way, we will build a flexible website while still benefiting from deploying static pages.

We'll be using the Nano text editor to create and edit our project files on the server. You can check the shortcuts cheatsheet for help using Nano. We'll also be using Uncomplicated Firewall (UFW) to control the traffic that is allowed in and out of the server. Our Next app uses port 3000, so we can enable incoming traffic via only this port using UFW.

  1. Allow incoming connections to port 3000, and reload the firewall.

    sudo ufw allow 3000
    sudo ufw reload
    
  2. Create a Next.js application sample-app.

    npx create-next-app@latest sample-app
    
  3. In the setup wizard, enter the following responses:

    ✔ Would you like to use TypeScript? … No
    ✔ Would you like to use ESLint? … Yes
    ✔ Would you like to use Tailwind CSS? … No
    ✔ Would you like to use `src/` directory? … Yes
    ✔ Would you like to use App Router? (recommended) … Yes
    ✔ Would you like to customize the default import alias (@/*)? … No
    
  4. Navigate into the project directory, and open the package.json file:

    cd sample-app
    nano package.json
    
  5. Change the start value, to access the application via server IP.

    "scripts": {
        "start": "next start -H 0.0.0.0",
    },
    
  6. Save and exit the file.

  7. Navigate into the src/app directory.

  8. Create a new directory posts within the src/app directory, and navigate into it.

  9. Create a JavaScript file called page.js

  10. Copy and paste the below code into the page.js file.

    import React from "react";
    import styles from "../page.module.css";
    
    async function getPosts() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      return res.json();
    }
    
    const posts = await getPosts();
    
    export default async function PostsPage() {
      return (
        <main className={styles.main}>
          <h1>Posts archive</h1>
          <ol>
            {posts.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ol>
        </main>
      );
    }
    
  11. Save and exit the file

  12. From the project root, create a production build and start the Next.js application in production mode:

    npm run build && npm run start
    

You should see something like this:

  Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (6/6)
✓ Collecting build traces
✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    5.42 kB        92.4 kB
├ ○ /_not-found                          871 B          87.9 kB
└ ○ /posts                               137 B          87.2 kB
+ First Load JS shared by all            87 kB
 ├ chunks/23-ef3c75ca91144cad.js        31.5 kB
 ├ chunks/fd9d1056-2821b0f0cabcd8bd.js  53.6 kB
 └ other shared chunks (total)          1.87 kB

○  (Static)  prerendered as static content

You can view the application at http://<server-ip>:3000/posts to see what we're generating at this point.

Let's look at the code we used to get this far. For fetching data, getPosts() sends a GET request to a URL that serves placeholder data. You might not make fetch requests in your application; you might instead get some information from data files on disk on the server or even query a local database. This example is helpful because we see what happens when we need external data from our website.

We're storing the request result as a variable posts that we can iterate over in the page template. The PostsPage function is the default export of the component, and it returns a JSX element that will be rendered as the component's output. We're iterating over each post returned from the endpoint and creating an ordered list of post titles.

You can stop the application by pressing Ctrl + C.

Understanding SSG in Next.js apps

Here are the key concepts you should understand when using SSG in Next.js apps, including the benefits they bring.

  • Pre-rendering
    • During the build process, all the pages of a website are pre-rendered into static HTML files. The content is generated once and stored as static files.
  • Content sources
    • You can source content from various places, such as a headless CMS, databases, or REST APIs.
    • The content is fetched and processed to create HTML files during the build process.
  • Deployment
    • You can cache and efficiently serve the built files using a web server or Content Delivery Network (CDN). If you know the files do not change, you can be more aggressive with caching, which is more efficient for visitors and your server costs.
    • You don't have to consider provisioning and maintaining an environment for server-side logic.

Adding dynamic routes to a static Next.js site

Now that we know how to fetch data from a public API at build time and populate a page template with the response for static pages, we might need to do something more complex.

We have a post archive at /posts that lists all the post titles, but we want to create individual pages for each post. We will use the numeric post id for this (e.g., /posts/12), but you can imagine it might also be interesting to use the title, for example, /posts/my-cool-post.

This poses an interesting problem because we might not know the IDs of the posts we're fetching at build time. How do we create template pages for every post? We can use dynamic routes to handle this case, which lets us add special handling to the routing but keeps static site generation as the preferred output.

To add dynamic routes for numeric blog IDs, make a directory with the special Next.js format [segmentName] (in this case [id]). If you're following these steps on a different machine using a shell like zsh, you will have to quote the square brackets like '[id]':

  1. Create a new directory [id] within the src/app/posts directory, and navigate into it.

  2. Create a new JavaScript file.

  3. Copy and paste the below code into the page.js file.

    import React from "react";
    import { notFound } from "next/navigation";
    import styles from "../../page.module.css";
    
    async function getPost(id) {
      const res = await fetch(
        `https://jsonplaceholder.typicode.com/posts/${id}`,
      );
      if (!res.ok) {
        return null;
      }
      return res.json();
    }
    
    export async function generateStaticParams() {
      const res = await fetch("https://jsonplaceholder.typicode.com/posts");
      const posts = await res.json();
    
      return posts.map((post) => ({
        id: post.id.toString(),
      }));
    }
    
    export default async function PostPage({ params }) {
      const post = await getPost(params.id);
    
      if (!post) {
        notFound();
      }
    
      return (
        <main className={styles.main}>
          <h1>{post.title}</h1>
          <p>{post.body}</p>
        </main>
      );
    }
    
  4. Save and close the file.

  5. Recreate the production build and restart the application:

    # in the project root:
    npm run build && npm run start
    

You should see the following:

Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (106/106)
✓ Collecting build traces
✓ Finalizing page optimization

Route (app)                              Size     First Load JS
┌ ○ /                                    5.42 kB        92.4 kB
├ ○ /_not-found                          871 B          87.9 kB
├ ○ /posts                               338 B          87.4 kB
└ ● /posts/[id]                          338 B          87.4 kB
   ├ /posts/1
   ├ /posts/2
   ├ /posts/3
   └ [+97 more paths]

Note that we've generated 106 pages this time, so the additional 100 posts are created at build time, and the page paths are pre-rendered depending on external data. If you want to explore other options, you can also look at getStaticPaths and the Next.js examples repository for other ways to add dynamic routes to an SSG build.

You can now visit the server at http://<server-ip>:3000/posts and explore each post at the numeric ID, like http://<server-ip>:3000/posts/2.

Practical uses and examples

Here are some examples where SSGs using Next.js work well:

  • Content-heavy websites: Blogs, documentation, and marketing sites where the content is relatively static and doesn't change much.
  • E-commerce: You can pre-render product pages so that you have fast-loading pages where conversions matter.
  • Jamstack architecture: Jamstack websites usually use JavaScript functionality on the client side while keeping the server-side static. If you're making a lot of network requests client-side, be aware that you might shift the performance cost to your visitors, so weigh this option carefully.

Conclusion

In this article, we created a Next.js application and deployed it to a Vultr NodeJS image. We learned how to use SSG to make a fast, scalable, and reasonably secure application. By learning how to use SSGs, you can create more resilient web applications with modern tooling and architecture.

This is a sponsored article by Vultr. Vultr is the world's largest privately-held cloud computing platform. A favorite with developers, Vultr has served over 1.5 million customers across 185 countries with flexible, scalable, global Cloud Compute, Cloud GPU, Bare Metal, and Cloud Storage solutions. Learn more about Vultr