NodeJS系列(8)- Next.js 框架 (一) | 安装配置、路由(Routing)、页面布局(Layout)


Next.js 是一个用于构建 Web 应用程序的框架。Next.js 是一个用于生产环境的 React 框架,是一个 React 服务端渲染应用框架。

Next.js 具有同类框架中最佳的 “开发人员体验” 和许多内置功能,它的特点如下:

    (1) 直观的、 基于页面 的路由系统(并支持 动态路由);
    (2) 预渲染,支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)
    (3) 自动代码拆分,提升页面加载速度;
    (4) 具有经过优化的预取功能的 客户端路由;
    (5) 内置 CSS 和 Sass 的支持,并支持多数 CSS-in-JS 库;
    (6) 开发环境支持快速刷新;
    (7) 利用 Serverless Functions 及 API 路由 构建 API 功能;
    (8) 完全可扩展;

Next.js 被用于数以万计的的网站和 Web 应用程序,包括许多世界上许多最大的品牌都在使用 Next.js。

NextJS: https://nextjs.org/
NextJS GitHub: https://github.com/vercel/next.js
React: https://www.reactjs.org/ (https://react.dev)
React GitHub: https://github.com/facebook/react


1. 系统环境

    操作系统:CentOS 7.9 (x64)
    NodeJS: 16.20.0
    NPM: 8.19.4
    NVM: 0.39.2
    NextJS: 13.4.12


2. 创建 Next.js 项目

    安装 create-next-app  脚手架,命令如下:

        # 使用 -g 参数,表示该命令只需在本机上运行一次
        $ npm install -g create-next-app       

            ...

    使用 create-next-app 命令创建 NextJS 项目,命令如下:

        $ create-next-app nextjs-demo

            √ Would you like to use TypeScript? ... No
            √ Would you like to use ESLint? ... No
            √ Would you like to use Tailwind CSS? ... No
            √ Would you like to use `src/` directory? ... Yes
            √ Would you like to use App Router? (recommended) ... No
            √ Would you like to customize the default import alias? ... Yes
            Creating a new Next.js app in /home/develop/nodejs/nextjs-demo.

            Using npm.

            Initializing project with template: default


            Installing dependencies:
            - react
            - react-dom
            - next


            added 23 packages in 5s
            Initialized a git repository.

            Success! Created nextdemo at /home/develop/nodejs/nextjs-demo


        注:在 Next.js 13 之前,Pages Router 是在 Next.jsp 中创建路由的主要方式。它使用直观的文件系统路由器将每个文件映射到一个路由。新版本的 Next.js 仍然支持 Pages Router,Next.js 官方建议迁移到新的 App Router,以利用 React 的最新功能。本文主要介绍基于 Pages Router 的 Web 应用,所以第 5 个选项选了 No,表示选择了 Pages Router。

    也可以使用 npx 命令直接创建 NextJS 项目,命令格式如下:

        $ npx create-next-app nextjs-demo

    注:npx 是 npm 5.2.0 及更高版本中包含的一个命令行工具,用于执行本地安装的或在线安装的 Node.js 包中的命令。npm 主要用于管理和安装依赖包,而 npx 主要用于运行本地或在线安装的包中的命令。

 

3. 运行 Next.js 项目

    1) 项目目录结构

        整个项目的目录结构:

            ├── public
            │   ├── favicon.ico     # Favicon
            │   ├── next.svg
            │   └── vercel.svg
            ├── src
            │   ├── pages               # Page Router
            │   │   ├── api
            │   │   │   └── hello.js
            │   │   ├── _app.js
            │   │   ├── _document.js
            │   │   └── index.js        # home Page
            │   │
            │   └── styles
            │       ├── globals.css
            │       └── Home.module.css
            │
            ├── jsconfig.json           # JavaScript 配置文件
            ├── next.config.js          # Next.js 配置文件
            ├── package-lock.json       # 项目依赖和脚本(锁定),运行 npm install 后自动生成
            ├── package.json            # 项目依赖和脚本
            └── README.md


    2) 启动项目

        进入 nextjs-demo 目录安装依赖,命令如下:

            $ npm install

        ...

       开发模式运行,命令如下:

            $ npm run dev

                > nextjs-demo@0.1.0 dev
                > next dev

                - ready started server on 0.0.0.0:3000, url: http://localhost:3000

...

 

        生产模式运行,命令如下:

            $ npm run build

                > nextjs-demo@0.1.0 build
                > next build
                
                ...


            $ npm run start

                > nextjs-demo@0.1.0 start
                > next start

                - ready started server on 0.0.0.0:3000, url: http://localhost:3000

                ...


        使用浏览器访问 http://ip:3000,本机上可以访问 http://localhost:3000。


4. 路由 (Routing)

    页面路由器 (Page Router) 有一个基于页面概念的文件系统路由器。当一个文件被添加到 src/pages (或 pages) 目录时,它会自动作为路由使用。

    在 Next.js 中,页面是从 src/pages (或 pages) 目录中的 .js、.jsx、.ts 或 .tsx 文件导出的 React 组件,并且每个页面都根据其文件名与一个路由相关联。

    1) 文件名路由

        在 src/pages 目录下创建 test.js 文件,内容如下:       
        

            export default (props) => {

                return (
                    <div>Test Page</div>
                )  
            }

       
        使用浏览器访问 http://localhost:3000/test,显示内容如下:

            Test Page

        显然 /test 被路由到 src/pages/test.js 文件,这里的路径是大小写敏感的,即 /TEST 和 /test 不是同一路径。

    2) 索引路由

        路由将自动将名为 index 的文件映射到 index 所在目录,具体规则如下:
        
            src/pages/index.js ->  /
            src/pages/hello/index.js  /hello

        在 src/pages 目录下创建 hello 子目录,并在 hello 目录下创建 index.js 文件,内容如下:

            export default (props) => {

                return (
                    <div>Hello - Index Page</div>
                )  
            }

                    
        使用浏览器访问 http://localhost:3000/hello,显示内容如下:

            Hello - Index Page           

    3) 嵌套路由 (多级目录+文件名)

        路由支持嵌套文件,具体规则如下:

            src/pages/hello/world.js  -> /hello/world
            src/pages/hello/nested/router.js -> /hello/nested/router

        在 src/pages 目录下创建 hello/nested 两级子目录,并在 nested 目录下创建 router.js 文件,内容如下:

            export default (props) => {

                return (
                    <div>Hello/nested - router Page</div>
                )  
            }

                    
        使用浏览器访问 http://localhost:3000/hello/nested/router,显示内容如下:

            Hello/nested - router Page   

    4) 动态路由

        Next.js 支持具有动态路由的页面,具体规则如下:
        
            src/pages/hello/[id].js  -> /hello/*

        在 src/pages/hello 目录下创建 "[id].js" 文件,内容如下:

            import { useRouter } from 'next/router'

            export default () => {

                const router = useRouter()
                return <p>Hello: {router.query.id}</p>

            }


        使用浏览器访问 http://localhost:3000/hello/5,显示内容如下:

            Hello: 5  

    5) API 路由

        使用 App Router 的项目,可以直接使用服务器组件或路由处理程序,不需要使用 API 路由。这里的 API 路由,是指在使用了 Page Router 的项目里,在文件夹 src/pages/api 目录中的任何文件,都会被自动映射到 /api/*,并将被视为 API 端点而不是页面。
        
        上文的 nextjs-demo 项目里,create-next-app 命令自动创建了文件 src/pages/api/hello.js,它就是一个 API 端点,内容如下:

            // Next.js API route support: https://nextjs.org/docs/api-routes/introduction

            export default function handler(req, res) {
                res.status(200).json({ name: 'John Doe' })
            }


        使用浏览器访问 http://localhost:3000/api/hello,显示内容如下:

            {"name":"John Doe"}

        API 路由在 next.config.js 中的相关配置属性是 pageExtensions。


5. 页面布局 (Layout)

    React 模型允许我们将页面解构为一系列组件,其中许多组件经常在页面之间重复使用,比如在每一页上都有相同的导航栏和页脚等。

    1) App 组件

        Next.js 使用 App 组件来初始化页面。App 组件除了控制页面初始化之外,还能完成如下功能:

            (1) 在页面更改之间创建共享布局
            (2) 将附加数据注入页面
            (3) 添加全局 CSS

        (1) 自定义 App 组件
        
            可以在 src/pages 目录下添加 _app.js 文件,内容如下:

                export default ({ Component, pageProps }) => {
                    return <Component {...pageProps} />
                }


            其中 Component 是动态页面(或当前页面),导航切换页面时,Component 都会更改为导航后的新页面。pageProps 是一个带有初始属性的对象,这些属性是通过某种数据获取方法为页面预加载的,否则它就是一个空对象。

            注:

                (1) 如果 App 组件正在运行,并且添加了自定义 App 组件(即新增了 src/pages/_app.js 文件),则需要重新启动 NextJS 服务。
                (2) App 组件 不支持的 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。


        (2) getInitialProps 属性

            在 App 组件中可以使用 getInitialProps 将对没有 getStaticProps 的页面禁用自动静态优化。

                import App from 'next/app'
                
                export default function MyApp({ Component, pageProps, example }) {
                    return (
                        <>
                        <p>Data: {example}</p>
                        <Component {...pageProps} />
                        </>
                    )
                }
                
                MyApp.getInitialProps = async (context) => {
                    const ctx = await App.getInitialProps(context)
                    
                    return { ...ctx, example: 'data' }
                }


            不建议使用此模式。相反,可以考虑逐步采用 App 路由器,这样可以更容易地获取页面和布局的数据。

    2) Document 组件

        Document 组件可以更新用于渲染 (render) 页面的 <html> 和 <body> 标记。

        (1) 自定义 Document 组件
        
            可以在 src/pages 目录下添加 _document.js 文件,内容如下:

                import { Html, Head, Main, NextScript } from 'next/document'
                
                export default function Document() {
                    return (
                        <Html lang="en">
                        <Head />
                        <body>
                            <Main />
                            <NextScript />
                        </body>
                        </Html>
                    )
                }


            注:
            
                (1) Document 组件仅在服务器上渲染 (render),因此 onClick 这类的事件处理程序不能在此文件中使用。
                (2) 要正确渲染页面,需要 <Html>、<Head/>、<Main/> 和 <NextScript/>。
                (3) _document.js 中使用的 <Head/> 组件与 next/head 不同。此处使用的 <Head/> 组件应仅用于所有页面通用的任何 <Head> 代码。对于所有其他情况,如<title>标记,建议在页面或组件中使用 next/head 。
                (4) <Main/> 之外的 React 组件不会被浏览器初始化。不要在此处添加应用程序逻辑或自定义 CSS(如样式 jsx)。
                (5) Document 组件不支持的 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。

        (2) 自定义 renderPage

            自定义 renderPage 是高级选项,只有在需要 CSS-in-JS 等库来支持服务器端渲染时才需要,对于内置样式的 jsx 支持来说是不需要的。

                import Document, { Html, Head, Main, NextScript } from 'next/document'
                    
                class MyDocument extends Document {
                    static async getInitialProps(ctx) {
                        const originalRenderPage = ctx.renderPage
                    
                        // Run the React rendering logic synchronously
                        ctx.renderPage = () =>
                        originalRenderPage({
                            // Useful for wrapping the whole react tree
                            enhanceApp: (App) => App,
                            // Useful for wrapping in a per-page basis
                            enhanceComponent: (Component) => Component,
                        })
                    
                        // Run the parent `getInitialProps`, it now includes the custom `renderPage`
                        const initialProps = await Document.getInitialProps(ctx)
                    
                        return initialProps
                    }
                    
                    render() {
                        return (
                            <Html lang="en">
                                <Head />
                                <body>
                                <Main />
                                <NextScript />
                                </body>
                            </Html>
                        )
                    }
                }

                export default MyDocument


            不建议使用此模式。相反,可以考虑逐步采用 App Router,这样可以更容易地获取页面和布局的数据。

            注:

                (1) 在客户端转换期间,不会调用 _document 中的 getInitialProps。
                (2) _document 的 ctx 对象等效于在 getInitialProps 中接收到的对象,添加了 renderPage。


    3) 自定义页面布局

        在上文 nextjs-demo 项目基础上,创建 src/components 目录,这里 components 和 pages 目录在同一级目录中。

        创建 src/components/layout.js 文件,内容如下:

            import Navbar from './navbar'
            import Footer from './footer'
            
            export default function Layout({ children }) {
                return (
                    <>
                        <Navbar />
                        <main>{children}</main>
                        <Footer />
                    </>
                )
            }


        创建 src/components/navbar.js 文件,内容如下:

            export default () => {
                return (
                    <>
                        <div>Navbar</div>
                    </>
                )
            }


        创建 src/components/footer.js 文件,内容如下:

            export default () => {
                return (
                    <>
                        <div>Footer</div>
                    </>
                )
            }

 

        修改 src/pages/_app.js 文件,内容如下:

            import Layout from '../components/layout'

            export default ({ Component, pageProps }) => {
                return (
                    <Layout>
                        <Component {...pageProps} />
                    </Layout>
                )
            }

 

        使用浏览器访问 http://localhost:3000/test,显示内容如下:

            Navbar
            Test Page
            Footer


    4) Errors 页面

        (1) 404 页面

            404 页面可能经常被访问。服务器为每次访问呈现一个错误页面会增加 Next.js 服务器的负载,这可能导致成本增加和体验缓慢。

            为了避免上述陷阱,Next.js 默认情况下提供了一个静态 404 页面,而无需添加任何额外的文件。

            自定义 404 页面,可以在 src/pages 添加 404.js 文件,内容如下:

                export default function Custom404() {
                    return <h1>404 - Page Not Found</h1>
                }

            注:如果需要在构建时获取数据,可以在此页面中使用 getStaticProps。

        (2) 500 页面

            服务器为每次访问呈现错误页面会增加响应错误的复杂性。为了帮助用户尽快获得对错误的响应,Next.js 默认情况下提供了一个静态 500 页面,而无需添加任何其他文件。
        
            自定义 500 页面,可以在 src/pages 目录下添加 500.js 文件,内容如下:
            
                export default function Custom500() {
                    return <h1>500 - Server-side error occurred</h1>
                }
            
            注:如果需要在构建时获取数据,可以在此页面中使用 getStaticProps。

        (3) Error 组件

            Error 组件同时处理客户端和服务器端的 500 错误。
            
            自定义 Error 组件,可以在 src/pages 目录下添加 _error.js 文件,内容如下:

                function Error({ statusCode }) {
                    return (
                        <p>
                        {statusCode
                            ? `An error ${statusCode} occurred on server`
                            : 'An error occurred on client'}
                        </p>
                    )
                }
                
                Error.getInitialProps = ({ res, err }) => {
                    const statusCode = res ? res.statusCode : err ? err.statusCode : 404
                    return { statusCode }
                }

                export default Error


            _error.js 仅在生产环境中使用。在开发过程中,一般调用堆栈中得到一个错误,以了解错误的来源。

        (4) 重用内置错误页

            要渲染内置错误页面,可以通过导入 Error 组件,格式如下:

                import Error from 'next/error'
    
                export async function getServerSideProps() {
                    const res = await fetch('https://api.github.com/repos/vercel/next.js')
                    const errorCode = res.ok ? false : res.status
                    const json = await res.json()
                
                    return {
                        props: { errorCode, stars: json.stargazers_count },
                    }
                }
                
                export default function Page({ errorCode, stars }) {
                    if (errorCode) {
                        return <Error statusCode={errorCode} />
                    }
                    
                    return <div>Next stars: {stars}</div>
                }

 

            注:

                Error 组件不支持 Next.js 数据获取方法,如 getStaticProps 或 getServerSideProps。
                _error 和 _app 一样,是一个保留的路径名,_error 用于定义错误页面的自定义布局和行为。当访问路径是 /_error 时,会自动跳转到 404 页面。

 

6. 使用 Ant Design

    Ant-Design 是采用 React 封装了一套 Ant Design 的组件库。Ant Design 的模块化设计方式,用户可以快速建立一个新的应用程序,它还提供丰富的 UI 组件和高可定制,用户可以完全按照自己的需求来进行个性化设置。

    Ant Design 支持跨端统一设计,并可以满足开发者因技术栈而异的不同设计习惯,能够帮助他们实现跨端开发的融合,加快项目实施的进度。Ant Design 还提供了丰富的文档,从概念引导到实践指南,用户可以利用文档获取设计新灵感,同时也提升了自己对软件设计,交互和用户体验的认知水平。

    Ant Desing: https://ant.design/index-cn

    1) 安装 Ant Design

        进入 NextJS 项目目录,运行如下命令:

            $ npm install antd

        命令运行成功后,查看 package.json 文件,在 "dependencies" 部分多了如下内容:

            {
                ...

                "dependencies": {
                    "antd": "^4.5.2",
                    ...

                }
            }


        以上命令安装了 latest 版的 antd,可以指定安装版本,本文使用 npm install antd@4.5.2 命令安装 Ant Design 4.5.2 。
        
        也可以先修改 package.json 文件,指定 antd 的版本,比如 "antd": "^4.5.2",再运行 npm install 命令。

    2) 使用 antd 组件

        在上文 nextjs-demo 项目基础上,修改 src/pages/_app.js 文件,内容如下:

            import 'antd/dist/antd.min.css';
            import Layout from '../components/layout'

            export default ({ Component, pageProps }) => {
                return (
                    <Layout>
                        <Component {...pageProps} />
                    </Layout>
                )
            }

            注:添加了 antd/dist/antd.min.css

 

        修改 src/components/navbar.js 文件,内容如下:

            import React, { useState } from 'react';
            import { HomeOutlined, LoginOutlined, SettingOutlined } from '@ant-design/icons';
            import { Menu } from 'antd';

            const items = [
                {
                    label: 'Home',
                    key: 'home',
                    icon: <HomeOutlined />,
                },
                {
                    label: 'Login',
                    key: 'login',
                    icon: <LoginOutlined />,
                },
            ];

            const Navbar = () => {
                const [current, setCurrent] = useState('home');

                const onClick = (e) => {
                    //console.log('click ', e);
                    setCurrent(e.key);
                };
                
                return <Menu onClick={onClick} selectedKeys={[current]} mode="horizontal" items={items} />;
            };

            export default Navbar;


        以上代码使用了 antd 的 Menu 组件和 Icon 组件,使用浏览器访问 http://localhost:3000/test,显示内容如下:

            Home  Login     # 菜单
            Test Page
            Footer


posted @ 2023-08-07 20:41  垄山小站  阅读(2929)  评论(0编辑  收藏  举报