Next.js

Next.js

2021/07/01

啟動專案

Next.js是個React框架,採用Next.js的好處是可以進行Static Site Generation (SSG)以及Server Side Rendering (SSR),讓系統的效能提升

產生一個Next.js專案

npx create-next-app

create-next-app會詢問專案名稱,並且新增一個專案的檔案夾,將檔案放在檔案夾下,如果要把檔案產生在現在的檔案夾下:

npx create-next-app .

執行專案

npm run dev

Next.js專案雖然是基於React,但是檔案結構與routing的方式是不太樣。

Pages

所有的檔案都放在pages檔案夾底下,其中預設會啟動的檔案是index.js

pages/index.js

import Head from 'next/head'

import Image from 'next/image'

import styles from '../styles/Home.module.css'


export default function Home() {

return (

<div className={styles.container}>

<Head>

<title>Create Next App</title>

<meta name="description" content="Generated by create next app" />

<link rel="icon" href="/favicon.ico" />

</Head>


<main className={styles.main}>

<h1 className={styles.title}>

Welcome to <a href="https://nextjs.org">Next.js!</a>

</h1>


<p className={styles.description}>

Get started by editing{' '}

<code className={styles.code}>pages/index.js</code>

</p>


<div className={styles.grid}>

<a href="https://nextjs.org/docs" className={styles.card}>

<h2>Documentation &rarr;</h2>

<p>Find in-depth information about Next.js features and API.</p>

</a>


<a href="https://nextjs.org/learn" className={styles.card}>

<h2>Learn &rarr;</h2>

<p>Learn about Next.js in an interactive course with quizzes!</p>

</a>


<a

href="https://github.com/vercel/next.js/tree/master/examples"

className={styles.card}

>

<h2>Examples &rarr;</h2>

<p>Discover and deploy boilerplate example Next.js projects.</p>

</a>


<a

href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"

className={styles.card}

>

<h2>Deploy &rarr;</h2>

<p>

Instantly deploy your Next.js site to a public URL with Vercel.

</p>

</a>

</div>

</main>


<footer className={styles.footer}>

<a

href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"

target="_blank"

rel="noopener noreferrer"

>

Powered by{' '}

<span className={styles.logo}>

<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />

</span>

</a>

</footer>

</div>

)

}

我們把內容簡化一下,我們就會發現就是react的語法,唯一不同的是,不需要import React

export default function Home() {

return (

<div>Hello</div>

)

}

我們再加一點內容:

pages/index.js

import {useState} from 'react';


export default function Home() {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<button onClick={handleClick}>{count}</button>

)

}

我們來新增一個元件

pages/product_list.js

export default function ProductList() {


return (

<ul>

<li>0 / iPad / 20000</li>

<li>1 / iPhone X / 30000</li>

</ul>

);

}

跟react一樣,可以將元件放在另一個元件(如:Home)裡面。

import {useState} from 'react';

import ProductList from './product_list';


export default function Home() {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<div>

<button onClick={handleClick}>{count}</button><br></br>

<ProductList/>

</div>

)

}


不一樣的是,可以利用檔名就可以連結到product_list了,省去了route的定義。最重要的是,每個頁面就會有固定的網址,有助於SEO。

pages/index.js

import {useState} from 'react';


export default function Home() {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<div>

<button onClick={handleClick}>{count}</button>

<br></br>

<a href ="product_list">Product List</a>

</div>

)

}

Style

在Next.js裡,可以對所有頁面套用css:

pages/_app.js

import '../styles/globals.css'


function MyApp({ Component, pageProps }) {

return <Component {...pageProps} />

}


export default MyApp

styles/globals.css

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;

}


a {

color: inherit;

text-decoration: none;

}


* {

box-sizing: border-box;

}

如果針對特定頁面(如:Home)要設定style

styles/Home.module.css

.container {

min-height: 100vh;

padding: 0 0.5rem;

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

height: 100vh;

}


.main {

padding: 5rem 0;

flex: 1;

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

}


.footer {

width: 100%;

height: 100px;

border-top: 1px solid #eaeaea;

display: flex;

justify-content: center;

align-items: center;

}


.footer a {

display: flex;

justify-content: center;

align-items: center;

flex-grow: 1;

}


.title a {

color: #0070f3;

text-decoration: none;

}


.title a:hover,

.title a:focus,

.title a:active {

text-decoration: underline;

}


.title {

margin: 0;

line-height: 1.15;

font-size: 4rem;

}


.title,

.description {

text-align: center;

}


.description {

line-height: 1.5;

font-size: 1.5rem;

}


.code {

background: #fafafa;

border-radius: 5px;

padding: 0.75rem;

font-size: 1.1rem;

font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,

Bitstream Vera Sans Mono, Courier New, monospace;

}


.grid {

display: flex;

align-items: center;

justify-content: center;

flex-wrap: wrap;

max-width: 800px;

margin-top: 3rem;

}


.card {

margin: 1rem;

padding: 1.5rem;

text-align: left;

color: inherit;

text-decoration: none;

border: 1px solid #eaeaea;

border-radius: 10px;

transition: color 0.15s ease, border-color 0.15s ease;

width: 45%;

}


.card:hover,

.card:focus,

.card:active {

color: #0070f3;

border-color: #0070f3;

}


.card h2 {

margin: 0 0 1rem 0;

font-size: 1.5rem;

}


.card p {

margin: 0;

font-size: 1.25rem;

line-height: 1.5;

}


.logo {

height: 1em;

margin-left: 0.5rem;

}


@media (max-width: 600px) {

.grid {

width: 100%;

flex-direction: column;

}

}

就跟react一樣,利用className套用style。

pages/index.js

import styles from '../styles/Home.module.css'

import {useState} from 'react';


export default function Home() {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<div className={styles.container}>

<main className={styles.main}>

<button className={styles.title} onClick={handleClick}>{count}</button><br></br>

<a

href="product_list"

className={styles.card}

>

<h2>Product</h2>

<p>Click here to see a product list</p>

</a>

</main>

</div>

)

}

Static Site Generation (SSG)

在Next.js可以利用getStaticProps來指定靜態的Prop。

import styles from '../styles/Home.module.css'

import {useState} from 'react';

//import ProductList from './product_list';

export const getStaticProps = () => {

return {

props: {

buildTimestamp: Date.now()

}

}

}


export default function Home({buildTimestamp}) {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<div className={styles.container}>

<main className={styles.main}>

App built at: {buildTimestamp}

<button className={styles.title} onClick={handleClick}>{count}</button><br></br>

<a

href="product_list"

className={styles.card}

>

<h2>Product</h2>

<p>Click here to see a product list</p>

</a>

</main>

</div>

)

}

當我們還是利用dev時,refresh網頁,buildTimestamp會一直更新。

npm run dev

然而,如果進行

npm run build

會產生靜態內容,再執行

npm run start

就會發現,refresh網頁時,buildTimestamp完全不會更新。也就是說,當某些網頁的內容更新頻率不高,可以利用這樣的特性,事先產生靜態內容,可以節省取得內容的時間。

Server Side Rendering (SSR)

server side rendering就是讓這個部分的執行發生在伺服器端,而不是瀏覽器端,只要把要執行的內容放在getServerSideProps()裡就可以了。

import styles from '../styles/Home.module.css'

import {useState} from 'react';

//import ProductList from './product_list';

export const getServerSideProps = () => {

return {

props: {

buildTimestamp: Date.now()

}

}

}


export default function Home({buildTimestamp}) {

const [count, setCount] = useState(0);


function handleClick() {

setCount(count+1);

}

return (

<div className={styles.container}>

<main className={styles.main}>

App built at: {buildTimestamp}

<button className={styles.title} onClick={handleClick}>{count}</button><br></br>

<a

href="product_list"

className={styles.card}

>

<h2>Product</h2>

<p>Click here to see a product list</p>

</a>

</main>

</div>

)

}