---
# System prepended metadata

title: next.js demo
tags: [next.js]

---

###### 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;
}
```
