---
# System prepended metadata

title: Next筆記

---

# Next筆記
## Automatic Setup
與CRA類似只要把create-react-app改成create-next-app
```
npx create-next-app@latest [projectName]
# or
yarn create next-app [projectName]
```
如需支援typescript只需新增--typescript flag
```
npx create-next-app@latest [projectName] --typescript
# or
yarn create next-app [projectName] --typescript
```
在pacage.json中你會看到一些scripts
```next
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
}
```

這些都是開發者可以調適的環境
* **dev** : run Next in development
* **build** : build Next to production
* **start** : start Next in production mode after run npm run build
* **lint** : set up Next.js' built-in ESLint configuration
## Pages
Pages 對於components來說是放置再pages資料夾裡，每一個components檔名就會對應到相對的pase

e.g. 我創建一個pages/about.js，這個components就會出現在/about上
```next
function About() {
  return <div>About</div>
}

export default About
```

## Pages with Dynamic Routes
Next 提供動態的路徑，只需要創建**pages/posts/[id].js**，這樣在**posts/1, posts/2**都會看到檔案

當你進到 **pages/post/[pid].js** 這個頁面時 :
```next
import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { pid } = router.query

  return <p>Post: {pid}</p>
}

export default Post
```
**useRouter** 是Next管理router的hooks，router裡面涵蓋相當多內容，比較於react-router-dom對Next來說router就涵蓋全部，可以透過router.push去對應的頁面，router.query就有對應的pidID，例如我導入pages/post/1，這時我的pid就是1。

### 以下以pages/post/[pid].js為例子
當我route到pages/post/abc，我對應到的query object　：
```next
{ "pid": "abc" }
```

當我route到pages/post/abc?foo=bar，我對應到的query object：
```next
{ "foo": "bar", "pid": "abc" }
```

但如果我們query的string與page parameters 命名依樣時例如我的route是/post/abc?pid=123，query object就會複寫 :
```ｎｅｘｔ
{ "pid": "abc" }
```
### 以下以pages/post/[pid]/[comment].js為例子
如果我route到/post/abc/a-comment，我的query object就會是
```next
{ "pid": "abc", "comment": "a-comment" }
```

考慮到我們clientSide的動態導入，next會用以下的方式 :
```next
import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link href="/post/abc">
          <a>Go to pages/post/[pid].js</a>
        </Link>
      </li>
      <li>
        <Link href="/post/abc?foo=bar">
          <a>Also goes to pages/post/[pid].js</a>
        </Link>
      </li>
      <li>
        <Link href="/post/abc/a-comment">
          <a>Go to pages/post/[pid]/[comment].js</a>
        </Link>
      </li>
    </ul>
  )
}

export default Home
```
是不是跟react-router-donm很像，唯一不同的點原本的to改成href就可以導入了。
> 註 : 把link與a tag比較雖然二者都可以導入到想要的頁面，但兩者確有不同的差別在於，a tag導到新頁面會將畫面refresh，link則是漸進式導入，簡單來說a tag會重整頁面，link跳轉頁面時畫面不會閃爍

### Catch all routes
但如果今天的router需要比較多階層的話，總不可能一直創建[id].js的檔案，容易造成巢狀地域。
next很貼心的只要新增pages/post/[...slug].js就可以捕捉所有的分頁
例如pages/post/[...slug].js 就會符合 /post/a, but also /post/a/b, /post/a/b/c等等.
而query object就會呈現以下樣子 :
```next
{ "slug": ["a", "b"] }
```

### Optional catch all routes
上面的Catch all routes有個缺陷就是無法使用在/post頁面，只要改成pages/post/[[...slug]].js就可以了對應的query object如下
```next
{ } // GET `/post` (empty object)
{ "slug": ["a"] } // `GET /post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)
```

### API Routes
用法語routes一樣只需要在url前面加api
For example, the API route pages/api/post/[pid].js has the following code:
```next
export default function handler(req, res) {
  const { pid } = req.query
  res.end(`Post: ${pid}`)
}
```
## Pre-rendering
Pre-rendering是next非重要的一環，除了可以提高使用者體驗，同時別於CRA更可以做到SEO指標，next有以下兩種Pre-rendering：
- Static Generation (Recommended): The HTML is generated at build time and will be reused on each request.
- Server-side Rendering: The HTML is generated on each request.

上敘是官方說法Static Generation會再使用者訪談頁面時regeneration對應的html檔案與一個JSON檔，如果你把next專案build起來，你會發現next是根據不同html去router你的page，雖然ssg看起來很美好，但還是有不足的地方，ＳＳＧ缺點就是會把所有頁面全部generation起來，所以你在build的時候會比較耗時，對user有善但對server是負擔，其二問題是api資料的更新並不會即時update而是使用者造訪頁面時才會rerender，所以才會有ＳＳＲ出現。

Server-side與Static Generation最大的差別是根據使用者request，直接跟server溝通，使用者進到頁面會丟一個request出來，當有資料更新頁面也會自動render不需要使用者重整頁面。

## getStaticProps
```next
function Blog({ posts }) {
  // Render posts...
}

// This function gets called at build time
export async function getStaticProps() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

export default Blog
```
getStaticProps就是典型的ssg，getStaticProps會在server上跑然後傳到props

## getStaticPaths
```next
// This function gets called at build time
export async function getStaticPaths() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}
```
簡單來說getStaticPaths裡面的paths就是告訴getStaticProps你要render哪些頁面

```next
function Post({ post }) {
  // Render post...
}

export async function getStaticPaths() {
  // ...
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

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

export default Post
```
getStaticProps裡的{params}就會去比對getStaticPaths裡的paths是否有match到

### fallback: false
告訴getStaticProps當進入的router沒有paths指定入徑就會導入404頁面
### fallback: true
使用 fallback: true 的使用比較複雜一點，因為與 fallback: false 不同的點在於，當使用者瀏覽沒有在 getStaticPaths 中定義的頁面時，Next.js 並不會回應 404 的頁面，而是回應 fallback 的頁面給使用者。

這個流程會呼叫 getStaticProps ，在伺服器產生資料前，使用者瀏覽的是 fallback 的頁面，在 getStaticProps 執行完後，同樣由 props 注入資料到網頁中，使用者這時就能看到完整的頁面。

而經過這個流程的頁面，該頁面會被加入到 pre-rendering 頁面中，下次如果再有同樣頁面的請求時，伺服器並不會再次的重新呼叫 getServerSideProps ，產生新的頁面，而是回應已經產生的頁面給使用者。

使用前幾個章節用到的產品詳細介紹頁面，由於在 getStaticPaths 中的 id 只有 '1' ，所以在 next build 階段只會生成 /products/1 這個頁面的 HTML，但是在設定 fallback: true 的情況下，當一位使用者瀏覽 /products/2 時， Next.js 會做以下幾件事情：

即使該頁面不存在 Next.js 不會回傳 404 頁面，Next.js 會開始動態地生成新的頁面。
在生成頁面時， router.isFallback 會一直為 true ，因次可以用條件式渲染 loading 情況下的頁面，而這時從 props 中拿到的 product 是 undefined。
而在 HTML 生成完畢後，使用者就會看到完整的商品介紹頁面

``` next
import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";

import { getProductById, Product as ProductType } from "../../fake-data";
import ProductCard from "../../components/ProductCard";
import { PageTitle, ProductContainer } from "./[id].style";

interface ProductProps {
  product: ProductType;
}

const Product = ({ product }: ProductProps) => {
  const router = useRouter();

  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <>
      <PageTitle>商品詳細頁面</PageTitle>
      <ProductContainer>
        <ProductCard product={product} all />
      </ProductContainer>
    </>
  );
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const product = getProductById(params?.id as string);

  return {
    props: {
      product,
    },
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  return {
    paths: [{ params: { id: "1" } }],
    fallback: true,
  };
};

export default Product;
```
### fallback: block
在 getStaticPaths 使用這個設定時，跟 fallback: true 一樣，在使用者瀏覽不存的頁面時，伺服器不會回傳 404 的頁面，而是執行 getStaticProps ，走 pre-rendering 的流程。

但是與 fallback: true 不一樣的點在於沒有 router.isFallback 的狀態可以使用，而是讓頁面卡在 getStaticProps 的階段，等待執行完後回傳結果給使用者。

所以使用者體驗會非常像似 getServerSideProps ，但優點是下次使用者再次瀏覽同一個頁面時，伺服器可以直接回傳已經生成的 HTML 檔案，往後甚至可以藉由 CDN 的 cache 提升頁面的載入速度。
## getServerSideProps
```next
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
```
getServerSideProps會在request產生頁面，而不是在buildTime

## Client-side data fetching with SWR
#### API
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options)
```next
import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

function Profile() {
  const { data, error } = useSWR('/api/profile-data', fetcher)

  if (error) return <div>Failed to load</div>
  if (!data) return <div>Loading...</div>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}

```
SWR優先返回緩存數據（stable），然後再遠端發送請求（revalidate），最後再更新數據，
useSWR接收key、feacher做為參數，然後返回data和error。

### 有条件的请求(Conditional Fetching)

```next
// 条件请求
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)

// 返回false
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fethcer)

// 抛出错误
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)
```
## Single Shared Layout with Custom App
```next
// pages/_app.js

import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}
```
_app為所有router的共用page只要新增layout所有頁面都可以共用
### 客製化共用組件
```next
function About() {
  
  return (
    <div>about</div>
  )
}
About.getPage = (page)=>{
  return page
   
}
export default About
```
註 ：在About新增getPage方法
```next
function MyApp({ Component, pageProps }) {
  if (Component.getPage){
    return Component.getPage(<Component {...pageProps} />)
  }
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

export default MyApp
```
這裡可以把Component想成每一個頁面，當我的頁面有getPage方法時我就render特定畫面已達到客製化組建方法

### Using the Image Component
```next
import Image from 'next/image'
import profilePic from '../public/me.png'

function Home() {
  return (
    <>
      <h1>My Homepage</h1>
      <Image
        src={profilePic}
        alt="Picture of the author"
        // width={500} automatically provided
        // height={500} automatically provided
        // blurDataURL="data:..." automatically provided
        // placeholder="blur" // Optional blur-up while loading
      />
      <p>Welcome to my homepage!</p>
    </>
  )
}
```
值得一提的是next的Image component很貼心，會自動偵測倒入的圖片畫面大小

## Image props
### priority
預先加載img的參數

### ObjectFit
等同使用css的objectfit

### blurDataURL
next的Image會有Lazying loading，blurDataURL就是當img在loading時呈現的模糊畫面，通常會搭配  placeholder='blur'一起使用


註 ：next很貼心的提供畫面模糊效果製作 [Demo](https://png-pixel.com/)
## next.config.js
### basePath
```jsonld
module.exports = {
  basePath: '/docs',
}
```

### Environment Variables
以往我們使用環境變數會用.env檔案去process，現在只要在next.config.js裡面新增就好
```NEXT
const nextConfig = {
  reactStrictMode: true,
  env:{
    customkey:'my-value'
  }
}
```
這樣當我們需要環境變數時直接使用就好
```next
const devEnvironment = process.env.NODE_ENV === 'development'
const pro = process.env.NODE_ENV === 'production'
const nextConfig = {
 env: {
    title: (() => {
      if (devEnvironment) return 'development';
      if (pro) return 'production';
    })()
  }
}
```
我們也可以透過判斷改變env變數結果
### Redirects
Next強大的ROUTER系統
```next
async redirects() {
    return [
      {
        source: '/home\\(default\\)',
        destination: 'https://www.youtube.com/watch?v=ndTh5KOIRVQ&ab_channel=PragmaticReviews',
        permanent: true,
        has: [
          {
            type: 'query',
            key: 'page',
            // the page value will not be available in the
            // destination since value is provided and doesn't
            // use a named capture group e.g. (?<page>home)
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
      }
    ]
  }
```
redirects功能是設定特定URL重新導向新頁面



**-source**　： 當前的URL這裡範例是以/home(default)為例子
**-destination**　： 需要導入的路徑
**-permanent**　： 改變瀏覽器狀態定義
**-has**　：檢查當前頁面是否有query與cookie內容


### Rewrites
告訴next當我輸入特定URL時，其實代表另一個page
```next
const pro = process.env.NODE_ENV === 'production'
const nextConfig = {
 async rewrites() {
    return {
      beforeFiles: [
        // These rewrites are checked after pages/public files
        // are checked but before dynamic routes
        {
          source: `${pro ? '/posts' : '/process'}`,
          destination: '/About',
        },
      ],
    }
  },
 }
```
rewrites有很多複寫時機點如beforeFiles、afterFiles、fallback這些都是告訴next，我的rewrites到底要在pages/public files比對前後取代page，因為有時我們的source可能已經有寫頁面了，如果沒有用beforeFiles，這樣next就不會去複寫，上述例子我刻意用pro測試環境當我在production mode的情況下要如何做writes。



| beforeFiles| afterFiles |fallback |
| -------- | -------- | -------- |
| public前     | public後| 調適完再導入     |


### Custom Webpack Config
```javascript
// Example config for adding a loader that depends on babel-loader
// This source was taken from the @next/mdx plugin source:
// https://github.com/vercel/next.js/tree/canary/packages/next-mdx
module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.mdx/,
      use: [
        options.defaultLoaders.babel,
        {
          loader: '@mdx-js/loader',
          options: pluginOptions.options,
        },
      ],
    })
    config.resolve: {
        alias: {
          '@img': path.resolve(__dirname, 'src/img'),
    },
  },
    return config
  },
}
```
next js 可以客製化webpack，把需要的套件載入與設置就好

