Contents

Next.js 学习笔记

Next.js 学习笔记

数据抓取

getServerSideProps

什么时候使用:

​ 仅当需要呈现其数据必须在请求时获取的页面时,才应使用 getServerSideProps 。这可能是由于请求的数据或属性的性质(例如 authorization 标头或地理位置)。使用 getServerSideProps 的页面将在请求时在服务器端呈现,并且只有在配置了缓存控制标头时才会被缓存。不能与getStaticProps同时使用

什么时候运行:

仅在服务器端运行,进入界面就会执行。

使用举例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page

getStaticProps

什么时候使用:

​ 用户请求之前数据是确定的。

创建项目

1
npx create-next-app

路由

1.可以使用目录直接导航

  1. pages目录下新建psots文件夹

  2. ./pages/psots下新建文件 first-post.tsx文件

    1
    2
    3
    4
    5
    6
    7
    
    export default function FirstPost() {
      return (
        <>
          <h1>First Post</h1>
        </>
      )
    }
    
  3. 访问http://localhost:3000/posts/first-post即可

2.使用Link导航

href中写明路径即可,以pages目录为跟路径。

1
2
3
import Link from "next/link";

<Link href="/posts/first-post">this page!</Link>
  1. index.tsx

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    import Head from 'next/head'
    import Link from "next/link";
    
    export default function Home() {
      return (
        <>
          <Head>
            <title>Create Next App</title>
            <meta name="description" content="Generated by create next app"/>
            <meta name="viewport" content="width=device-width, initial-scale=1"/>
            <link rel="icon" href="/favicon.ico"/>
          </Head>
          Hello Next
          Read <Link href="/posts/first-post">this page!</Link>
        </>
      )
    }
    
  2. /psots/first-post.tsx

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    import Link from "next/link";
    
    export default function FirstPost() {
      return (
        <>
          <h1>First Post</h1>
          <h2>
            <Link href="/">
              Back to home
            </Link>
          </h2>
        </>
      )
    }
    

3.使用函数导航

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  const toPost = (id: string) => {
    router.push(
      "/posts/" + id, //跳转url
      "/posts/" + id, //在浏览器URL栏中显示的路径
      {
        scroll: true, // 可选布尔值,控制导航后滚动到页面顶部。默认为 true
        shallow: false, // 更新当前页面的路径而不重新运行 getStaticProps 、 getServerSideProps 或 getInitialProps 。默认为 false
        locale: "zh" // 可选字符串,表示新页面的语言环境
      })
  }
  //使用
  
<a onClick={() => toPost(id)}>
  {title}
</a>

静态资源

在顶级 public 公共目录下提供静态文件

public文件夹下有文件next.svg

index.tsx,src以public为跟路径

1
2
3
4
5
6
      <Image
        src="/next.svg"
        alt="Next.js Logo"
        width={180}
        height={37}
      />

图像优化

图片组件

1
import Image from 'next/image'

本地图片使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Image from 'next/image'
import profilePic from '../assets/me.png'

function Home() {
  return (
    <>
      <Image
        src={profilePic}
        alt="Picture of the author"
      />
    </>
  )
}

远程图片必须填写widthheight

1
2
3
4
5
6
 <Image
        src="/me.png"
        alt="Picture of the author"
        width={500}
        height={500}
      />

元数据

pages/index.tsxHead组件中编写

1
2
3
4
5
6
import Head from 'next/head'

<Head>
  <title>Create Next App</title>
  <link rel="icon" href="/favicon.ico"/>
</Head>

如果在其他界面中添加Head组件则会出现覆盖效果,比如在/psots/first-post.tsx中添加Head组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import Link from "next/link";
import Head from "next/head";

export default function FirstPost() {
  return (
    <>
      <Head>
        <title>ONE</title>
      </Head>
      <h1>First Post</h1>
      <h2>
        <Link href="/">
          Back to home
        </Link>
      </h2>
    </>
  )
}

CSS Module

  1. 在跟路径下创建components文件夹用来存放组件

  2. components文件夹中创建 layout.tsx

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    import React from "react";
    
    type Props = {
      children: React.ReactNode
    }
    
    export default function Layout({children}: Props) {
      return (
        <div>{children}</div>
      )
    }
    
  3. pages/posts/first-post.tsx 中,导入 Layout 并使其成为最外层的组件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    import Link from "next/link";
    import Head from "next/head";
    import Layout from "@/components/layout";
    
    export default function FirstPost() {
      return (
        <Layout>
          <Head>
            <title>ONE</title>
          </Head>
          <h1>First Post</h1>
          <h2>
            <Link href="/">
              Back to home
            </Link>
          </h2>
        </Layout>
      )
    }
    
  4. components文件夹中创建layout.module.css

    1
    2
    3
    4
    5
    
    .container {
        max-width: 36rem;
        padding: 0 1rem;
        margin: 3rem auto 6rem;
    }
    
  5. layout.tsx中使用css文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    import React from "react";
    import styles from './layout.module.css'
    
    type Props = {
      children: React.ReactNode
    }
    
    export default function Layout({children}: Props) {
      return (
        <div className={styles.container}>{children}</div>
      )
    }
    
  6. 观察界面效果

Global CSS

  1. pages 下创建 _app.tsxApp 组件是顶级组件,它将在所有不同的页面中通用,在next.js中只能在pages/_app.tsx中添加全局css

    1
    2
    3
    4
    5
    6
    7
    
    import type {AppProps} from 'next/app'
    
    export default function App({Component, pageProps}: AppProps) {
      return (
          <Component {...pageProps} />
      )
    }
    
  2. 在跟路径下创建styles文件夹

  3. styles文件夹下创建global.css

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    html,
    body {
      padding: 0;
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
      Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
      line-height: 1.6;
      font-size: 18px;
    }
    
    * {
      box-sizing: border-box;
    }
    
    a {
      color: #0070f3;
      text-decoration: none;
    }
    
    a:hover {
      text-decoration: underline;
    }
    
    img {
      max-width: 100%;
      display: block;
    }
    
  4. _app.tsx中引入

    1
    
    import '@/styles/globals.css'
    
  5. 观察页面Link变化

预渲染

可以带来更好的性能和 SEO

构建时实现带数据的静态生成

  1. 在跟路径下创建posts文件夹

  2. posts文件夹创建一个md文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    ---
    title: 'Two Forms of Pre-rendering'
    date: '2020-01-01'
    ---
    
    Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
    
    - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
    - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
    
    Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
    
  3. 实现解析文件获取titledate

    1. 安装gray-matte解析markdown 文件中的元数据

      1
      
      npm install gray-matter
      
    2. 在跟路径下创建lib文件夹

    3. 创建posts.js文件

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      
      import fs from 'fs'
      import path from 'path'
      import matter from 'gray-matter'
      
      const postsDirectory = path.join(process.cwd(), 'posts')
      
      export function getSortedPostsData() {
        // Get file names under /posts
        const fileNames = fs.readdirSync(postsDirectory)
        const allPostsData = fileNames.map(fileName => {
          // Remove ".md" from file name to get id
          const id = fileName.replace(/\.md$/, '')
      
          // Read markdown file as string
          const fullPath = path.join(postsDirectory, fileName)
          const fileContents = fs.readFileSync(fullPath, 'utf8')
      
          // Use gray-matter to parse the post metadata section
          const matterResult = matter(fileContents)
      
          // Combine the data with the id
          return {
            id,
            ...matterResult.data
          }
        })
        // Sort posts by date
        return allPostsData.sort((a, b) => {
          if (a.date < b.date) {
            return 1
          } else {
            return -1
          }
        })
      }
      
  4. index.tsx界面处理

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    
    import Head from 'next/head'
    import Layout, {siteTitle} from '../components/layout'
    import utilStyles from '../styles/utils.module.css'
    import {getSortedPostsData} from '@/lib/posts'
    
    type PostsData = {
      id: string,
      date: string,
      title: string
    }
    type AllPostsData = {
      allPostsData: PostsData[]
    }
    export default function Home({allPostsData}: AllPostsData) {
    
    
      return (
        <Layout home={true}>
          <Head>
            <title>{siteTitle}</title>
          </Head>
          <section className={utilStyles.headingMd}>
            <p>自我介绍</p>
          </section>
          <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
            <h2 className={utilStyles.headingLg}>Blog</h2>
            <ul className={utilStyles.list}>
              {allPostsData.map(({id, date, title}) => (
                <li className={utilStyles.listItem} key={id}>
                  {title}
                  <br/>
                  {id}
                  <br/>
                  {date}
                </li>
              ))}
            </ul>
          </section>
        </Layout>
      )
    }
    
    export async function getStaticProps() {
      const allPostsData = getSortedPostsData()
      return {
        props: {
          allPostsData
        }
      }
    }
    

请求时

使用getServerSideProps

动态路由

在目录中或文件中使用[xx][xx].tsx即可通过url访问

多级:[...all].tsx

  1. 创建pages/posts/[id].tsx文件,

    getStaticPaths方法需要返回所有可能有的id的列表

    getStaticProps方法需要返回指定id的数据

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    import Layout from '../../components/layout'
    
    export default function Post() {
      return <Layout>...</Layout>
    }
    
    export async function getStaticPaths() {
    
    }
    
    export async function getStaticProps({ params }) {
    
    }
    
  2. lib/posts.js中添加功能

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    export function getAllPostIds() {
        const fileNames = fs.readdirSync(postsDirectory)
        return fileNames.map(fileName => {
            return {
                params: {
                    id: fileName.replace(/\.md$/, '')
                }
            }
        })
    }
    
  3. 完善pages/posts/[id].tsxgetStaticPaths方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    import { getAllPostIds } from '../../lib/posts'
    
    export async function getStaticPaths() {
      const paths = getAllPostIds()
      return {
        paths,
        fallback: false
      }
    }
    
  4. lib/posts.js中添加功能

    1
    
    npm install remark remark-html
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    import {remark} from 'remark'
    import html from 'remark-html'
    
    export async function getPostData(id) {
        const fullPath = path.join(postsDirectory, `${id}.md`)
        const fileContents = fs.readFileSync(fullPath, 'utf8')
    
        // Use gray-matter to parse the post metadata section
        const matterResult = matter(fileContents)
    
        // Use remark to convert markdown into HTML string
        const processedContent = await remark()
            .use(html)
            .process(matterResult.content)
        const contentHtml = processedContent.toString()
    
        // Combine the data with the id and contentHtml
        return {
            id,
            contentHtml,
            ...matterResult.data
        }
    }
    
  5. 完善pages/posts/[id].tsxgetStaticProps方法

    1
    2
    3
    4
    5
    6
    7
    8
    
    export async function getStaticProps({params}: any) {
      const postData = await getPostData(params.id)
      return {
        props: {
          postData
        }
      }
    }
    
  6. 完善pages/posts/[id].tsx

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    export default function Post({postData}: any) {
      return (
        <Layout home={false}>
          <Head>
            <title>{postData.title}</title>
          </Head>
          <article>
            <h1 className={utilStyles.headingXl}>{postData.title}</h1>
            <div className={utilStyles.lightText}>
              {postData.date}
            </div>
            <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
          </article>
        </Layout>
      )
    }