# 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就會複寫 :
```next
{ "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看起來很美好,但還是有不足的地方,SSG缺點就是會把所有頁面全部generation起來,所以你在build的時候會比較耗時,對user有善但對server是負擔,其二問題是api資料的更新並不會即時update而是使用者造訪頁面時才會rerender,所以才會有SSR出現。
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,把需要的套件載入與設置就好