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
// 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
// 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;
// page/profile/index.js import styles from '../../styles/Profile.module.css'; function ProfileDefault() { return ( <div className={styles.profile}>ProfileDefault page</div> ); } export default ProfileDefault;
// 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檔案
// 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)
// 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;
// component/ Footer import styles from '../styles/Footer.module.css'; function Footer() { return ( <div className={styles.footer}>Footer</div> ); } export default Footer;
// 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
// 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兩種拿資料的方法
// 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
Next render type:CSR, SSR, SSG, ISR20


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