###### tags: `next.js`
# next.js demo
## Intro
- Next.js 是一個基於 React 的框架,它同時支援 SSR (Server Side Rendering) 與 SSG (Static Side Generation) 兩種方法,不需要很多的設定就可以讓網站同時有這兩種功能。
### Steps
- `npx create-next-app@latest`
- What is your project named? › my-app
- 直接執行 `npm run dev` 就可以在 localhost:3000 啟動 Next.js 專案
- `npm run start` 是給 Production 發佈用, 執行前先跑 `npm run build`
- add static router in page
- Next.js 為約定目錄所以開發時起始位置是在 pages 的這個目錄
- 在page file 已經有index.js檔(root->'/'), router就是檔案名稱(ex: about, profile, users)
- 圖片可自己選一張 https://www.freepik.com/search?format=search&query=stock
- 建一個imgs file在root下, 將下載下來的圖片放到這個資料夾
- 改index.js code貼上以下code
```javascript=
// page/index.js
import Head from 'next/head';
import Image from 'next/image';
import profilePic from '../imgs/profile.jpg';// 放自己的圖片
import styles from '../styles/Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Next Demo</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h3 className={styles.title}>Home page</h3>
<Image
src={profilePic}
alt="Picture of the author"
width={400}
height={300}
/>
</main>
</div>
)
}
```
- router
- default router
- 在page建 about.js檔
- 在page建一個資料夾 profile, 裡面建index.js當作default檔案, default /profile 就是index.js
- 在page建 404.js檔當作錯誤404 page
```javascript=
// page/about
import { useState, useRef } from 'react';
import styles from '../styles/About.module.css';
function About() {
const [text, setText] = useState('');
const inputRef = useRef('');
const updateText = () => {
console.log('這是在client side執行唷!');
setText(inputRef.current.value);
inputRef.current.value = '';
};
return (
<div className={styles.about}>
<div>This is about page!</div>
<div>輸入的值: {text}</div>
<input type='text' ref={inputRef} />
<button onClick={()=>{updateText()}}>更新</button>
</div>
);
}
export default About;
```
```javascript=
// page/profile/index.js
import styles from '../../styles/Profile.module.css';
function ProfileDefault() {
return (
<div className={styles.profile}>ProfileDefault page</div>
);
}
export default ProfileDefault;
```
```javascript=
// page/404.js
import Link from 'next/link';
import styles from '../styles/NotFound.module.css';
function PageNotFound() {
return (
<div className={styles.notFound}>
<div>Page Not Found</div>
<Link href='/'>Go Home</Link>
</div>
);
}
export default PageNotFound;
```
- add dynamic router: [username].js
- 在資料夾 profile 裡面建[username].js檔案, 實作動態路由, 當路徑為profile/emma指的就是[username].js檔案
```javascript=
// page/profile/[username].js
import { useRouter } from 'next/router';
import styles from '../../styles/Profile.module.css';
function Profile() {
const router = useRouter();
const { username } = router.query;
return (
<div className={styles.profile}>Hello {username}!</div>
);
}
export default Profile;
```
- layout使用:建立components file資料夾在root, 裡面建
- Navbar.js
- Footer.js
- Layout.js (將Navbar, Footer放進Layout)
```javascript=
// componet/Navbar
import Link from 'next/link';
import styles from '../styles/Navbar.module.css';
function Navbar() {
return (
<div className={styles.navbar}>
<div className={styles.item}>
<Link href='/'>Home</Link>
</div>
<div className={styles.item}>
<Link href='/about'>About</Link>
</div>
<div className={styles.item}>
<Link href='/profile'>Profile</Link>
</div>
<div className={styles.item}>
<Link href='/users'>Users</Link>
</div>
</div>
);
}
export default Navbar;
```
```javascript=
// component/ Footer
import styles from '../styles/Footer.module.css';
function Footer() {
return (
<div className={styles.footer}>Footer</div>
);
}
export default Footer;
```
```javascript=
// component/layout
import Navbar from './Navbar';
import Footer from './Footer';
function Layout({children}) {
return (
<>
<Navbar />
{children}
<Footer />
</>
);
}
export default Layout;
```
- 建utils file, 裡面建api.js檔, 寫預設 api
```javascript=
// utils/api.js
const api = {
hostname: 'https://randomuser.me/api/?results=5',
getUsers() {
console.log('123');
return fetch(`${this.hostname}`)
.then((response) => response.json());
},
};
export default api;
```
- 建一個user file
- fetch: `getServerSideProps` v.s `getStaticProps`兩種拿資料的方法
```javascript=
// page/users/index.js
import api from '../utils/api';
import Image from 'next/image';
import styles from '../../styles/Users.module.css';
function Users({users}) {
const renderUsers = () => {
const output = users.map((user) => {
return (
<div className={styles.user} key={user.login.uuid}>
<Image
src={user.picture.large}
alt='Picture of the user'
width={80}
height={80}
/>
<h4>Name: {user.name.first} {user.name.last}</h4>
<p>Email: {user.email}</p>
</div>
)
})
return output;
};
return (
<div className={styles.userContainer}>{renderUsers()}</div>
);
}
export const getServerSideProps = async () => {
const { results } = await api.getUsers();
return {
props: {
users: results,
},
};
};
export default Users;
// export const getStaticProps = async () => {
// const { results } = await api.getUsers();
// return {
// props: {
// users: results,
// //revalidate: 20, // 每 20 秒就會 revalidate 一次頁面
// },
// };
// };
```
- api: server side
- 點擊users page在server端打api, console出 '123'
- 輸入about page input 更新畫面, 假裝打api的console也會是在client side
## getServerSideProps與getStaticProps差異?
- getServerSideProps是在client發送request的時候執行拿資料 ex: 高SEO的電商
- getStaticProps是在npm run build時拿資料 ex: 靜態網頁如需要高SEO的官網
## How to Choose
Next.js 可依專案需求支援不同頁面決定要使用 CSR, SSR, SSG, ISR 等不同的渲染方式。
* 資料會不斷更新,且不需要 SEO 的頁面,可以考慮用 CSR,ex: 內部系統 dashboard。
* 資料會不斷更新,且需要 SEO 的頁面,可以考慮用 SSR。
需留意使用了 SSR 就要考慮 Server 端要處理的事情,因此可視專案架構、是否所有頁面都有 SEO 的需求,來決定 SSR 與 CSR 搭配的比例, ex: 電商下單頁面。
* 資料幾乎不用更新且需要 SEO,可以考慮用 SSG,ex: 部落格, 官網。
* 資料不需要頻繁更新且需要 SEO,可以考慮用 ISR(Incremental Static Regeneration),ex: 電商商品頁。
## Ref
[Next.js](https://nextjs.org/docs/getting-started)
[Next render type:CSR, SSR, SSG, ISR20](https://next-render.theodorusclarence.com/render/isr-20)
------------------
CSS參考:
```CSS=
// About.module.css
.about {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 80vh;
font-size: 20px;
}
// Profile.module.css
.profile {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
font-size: 20px;
}
// Users.module.css
.userContainer {
text-align: center;
}
.user {
padding-bottom: 30px;
}
.userContainer h4{
margin: 0;
}
.userContainer p {
margin: 0;
}
// Navbar.module.css
.navbar {
display: flex;
justify-content: center;
padding: 20px;
background-color: black;
color: white;
font-size: 20px;
}
.item {
padding-right: 20px;
}
// Footer.module.css
.footer {
display: flex;
justify-content: center;
padding: 20px;
background-color: black;
color: white;
font-size: 20px;
}
// NotFound.module.css
.notFound {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 80vh;
font-size: 20px;
}
.notFound a:hover {
color: aqua;
}
```