Skip to main content

[Next] Next.js 筆記

此筆記為 Next.js Tutorial For Beginners 的學習紀錄

起專案

npx create-next-app my-prac

可選擇 typescript, eslint, tailwind ,還有下面預設為 no 兩個問題

Would you like to use `src/` directory with this project? » No / Yes      
Would you like to use experimental `app/` directory with this project? » No / Yes

所有檔案都在 pages 資料夾,修改 index.js 來呈現不同的首頁畫面。

最後,查看 package.jsonnpm run dev 啟動專案

Pages & Routes

pages 資料夾下的檔案,都有預設的路徑,如果建立一個 about.js 的檔案,裡面的內容預設會呈現在 http://localhost:3000/about

從上面圖片可看出,在 pages 裡面建立子資料夾,裡面也會有對應的路徑,因此在 pages/ninjas/index.tsx 的內容,會顯示在 localhost:3000/ninjas

Reusable dropping components

在 React.js 的撰寫中,會有許多重複使用的元件,他們不需要有自己的路徑,我們可以建立一個獨立的資料夾(comps之類的名字)來儲存他們,之後再到主要元件裡面進行引用。

Linking Between Pages

在元件內匯入 link 模組,便可以透過 link 來去切換元件

import Link from "next/link";
// ...
<Link href={"/ninjas"}>See Ninja Listing</Link>

值得一提的是,next.js 會在該頁面元件匯入需要的 javascript 模組,可以使用 網頁檢查 裡面的 Network 來去看到,而因為使用 link 模組,那些元件所需要的 javascirpt 也會在背景預先被載入(prefetch),來去提升使用者體驗。

Layout Component

comps 資料夾底下建立 Layout.tsx

next-prac\comps\Layout.tsx
import Footer from "./Footer";
import Navbar from "./Navbar";
import { ReactNode } from "react"; // React 節點 for TS

const Layout = ({ children }: { children: ReactNode }) => {
return (
<div className="content">
<Navbar />
{children}
<Footer />
</div>
);
};

export default Layout;

接著,在 _app.js 裡面,原本只有回傳 Component ,加上 Layout 包住整個網頁的原件

next-prac\pages_app.tsx
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}

如此一來,就可以在所有有路徑的元件中,帶入我們建立好的 NavbarFooter 元件。

Adding Styles

在 'styles' 資料夾裡面原本有 globals.css ,這是全域的 css ,而我們可以針對特定的檔案,來去建立 .module.css

我們建立 Home.module.css

next-prac\styles\Home.module.css
.title{
...
}
.text {
...
}
.btn {
...
}

接著在 index.tsx 使用他

next-prac\pages\index.tsx
import styles from "../styles/Home.module.css";
// ...
<h1 className={styles.title}>Hello, world!</h1>
<p className={styles.text}>
// ...
</p>

最後,值得注意的是在 .module.css 裡面只能使用 class 的選擇器,因為引用的 html 元素也是在 className 使用那些 css。

404 page

pages 資料夾裡面建立 404.tsx 的檔案,next.js 會自動將找不到路徑的元件,帶入這個元件。此元件可以透過 link 來指引使用者回去首頁等地方。

next-prac\pages\404.tsx
import Link from "next/link";

const NotFound = () => {
return (
<div className="not-found">
<h1>Oooops...</h1>
<h2>That page cannot be found.</h2>
<p>
Go back to the <Link href="/">Homepage</Link>
</p>
</div>
);
};

export default NotFound;

Redirect

透過 next.js 的 useRouter 裡面的方法,我們可以重新導向不同的路徑,再搭配上 useEffect,可以控制多久時間來執行

next-prac\pages\404.tsx
import { useEffect } from "react";
import { useRouter } from "next/router";

const NotFound = () => {
const router = useRouter();

useEffect(() => {
const timeout = setTimeout(() => {
router.back();
// router.push("/"); 回到首頁
}, 3000);

return () => {
clearTimeout(timeout);
};
}, []);

// ...

Images & Metadata

Navbar ,我想加入一張 logo ,首先要去 public 資料夾裡面放入圖片 ,接著透過 next.js 的 image 模組,我們可以指定寬高,它會自動去依據指定寬高縮放。值得注意的是,圖片會在該元素滑鼠滑到時才進行載入,達到優化效能的結果。

next-prac\comps\Navbar.tsx
import Image from "next/image";
// ...
<div className="logo">
<Image src={"/logo.png"} alt={"ninja logo"} width={128} height={77} />
</div>

關於 metadata ,我們會想要加入些 title 或是其他 keyword 的屬性,來幫助網站的 SEO,這些屬性都是放在 HTML 的 head 標籤裡面,在 next.js 匯入 head 模組來達成

next-prac\pages\index.tsx
import Head from "next/head";

export default function Home() {
return (
<>
<Head>
<title>Ninja List | Home</title>
<meta name="keywords" content="ninjas"></meta>
</Head>
// ...

這樣一來,就可以看到網頁上的標題改變了!

Fetching Data(getStaticProps)

理想中,我們想要在元件開始生成之前取得資料,透過 next.js 提供的特殊函式: getStaticProps ,這個函式只在網頁一開始生成的時候跑,而不是網頁生成後在瀏覽器中執行。

next-prac\pages\ninjas\index.tsx
export const getStaticProps = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();

return {
props: { ninjas: data },
};
};

getStaticProps 是個要 export 出去的函式,透過上面函式,我們獲得 props: {ninjas: data} ,可以將結果帶入元件的 UI 之中。

next-prac\pages\ninjas\index.tsx
interface Ninja {
id: number;
name: string;
}

interface NinjasProps {
ninjas: Ninja[];
}

const Ninjas = ({ ninjas }: NinjasProps) => {
return (
<div>
<h1>All Ninjas</h1>
{ninjas.map((ninja) => (
<div key={ninja.id}>
<a className={styles.single}>
<h3>{ninja.name}</h3>
</a>
</div>
))}
</div>
);
};

產出所有的忍者如下:

Dynamic Routes

有時候,我們需要依據不同的 id 來產生不同的頁面元件,他們可能大體一致,因此我們可以製作一個共用的元件,在 next.js 的 pages 資料夾裡面都是相對應的路徑,而在檔案名的外面加入 [] ,會使得該檔案的路徑變為動態路徑,如下,每個通過 /ninjas/id ,任意的 id 都會去到相同的元件。

接著,在上面的 Ninja list ,我們可以改寫每個 ninja 的連結,連結到特定 id 的路徑,如下:

next-prac\pages\ninjas\index.tsx
const Ninjas = ({ ninjas }: NinjasProps) => {
return (
<div>
<h1>All Ninjas</h1>
{ninjas.map((ninja) => (
<Link
href={"/ninjas/" + ninja.id}
className={styles.single}
key={ninja.id}
>
<h3>{ninja.name}</h3>
</Link>
))}
</div>
);
};

getStaticPaths

由於 [id].tsx 是動態路徑,透過 getStaticPaths,next.js 可以知道要產生多少個路徑。例如 /ninjas/1、/ninjas/2 等等。在這些路徑中,1 和 2 等數字是參數值,可以通過路徑的方式動態地傳遞給頁面組件。

next-prac\pages\ninjas[id].tsx
export const getStaticPaths: GetStaticPaths = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data: Ninja[] = await res.json();

const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});

return {
paths: paths,
fallback: false, // beyond the scope, id doesn't exist, go to 404
};
};

接著,透過前面介紹的 getStaticProps 取得對應路徑的資料,放入 UI 之中。

next-prac\pages\ninjas[id].tsx
export const getStaticProps: GetStaticProps<DetailsProps> = async (context) => {
const id = context.params?.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data: Ninja = await res.json();

return {
props: { ninja: data },
};
};

const Details = ({ ninja }: DetailsProps) => {
return (
<div>
<h1>{ninja.name}</h1>
<p>{ninja.email}</p>
<p>{ninja.website}</p>
<p>{ninja.address.city}</p>
</div>
);
};

最後,我們透過 npm run build ,來看產生的檔案(next-prac\.next\server\pages\ninjas),會發現的確剛剛好有 10 筆資料的 html 和 json。

Deploying to Vercel

Vercel 和 Next.js 都是同一群建立的,所以很好部署。Vercel 可以用 github 註冊,登入後點擊 add new

再來選擇 Import Git Repository

下面選擇自己要部署的資料夾,引入後會出現

因為這個專案已經有 build 好的檔案,且不需要 enviroment variable ,所以直接按 deploy 就可以部署完成。

最後的成果,就可以點回首頁,點擊該專案的特定網址前去觀看,若是要改 domain ,可以再去 view domains 裡面進行編輯,但沒有付費的應該只能改的有限。

參考資源