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 →</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 →</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 →</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 →</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>
)
}