Front-End
React
Next.js
這篇主要用來記錄 Next.js 開發過程採的坑,後續也會不定期更新:
"Window is not defined"
errormissing generateStaticParams()
error事情發生在開發 Next.js APP,要實作 Google 登入驗證,需要使用 Window、Document 物件時,發現程式會報出以下錯誤:
ReferenceError: window is not defined
or
ReferenceError: document is not defined
這是由於 Next.js 預設為伺服器渲染(Server-side Rendering),會在 Node.js 環境下預渲染頁面,並將生成的 HTML 內容發送給 Client 端。
因此渲染過程是在 Server 端而非在瀏覽器中,由於程式無法識別 Window / Document 物件而回報上述 Error。
解決方法可透過條件渲染(Conditional Rendering),確保 Next.js 只在 Client 端執行指定的程式碼,
可透過以下兩種方式來實現:
判斷 window 是否存在,確定在瀏覽器中才執行指定的程式碼:
const isBrowser = () => typeof window !== 'undefined';
if (typeof window !== 'undefined') {
// Client-side-only
console.log('window: ', window);
};
透過 useEffect 等方法,可確保程式碼只會在 Client 端執行:
'use client';
import React, { useEffect } from 'react';
// ...
useEffect(() => {
// Client-side-only
console.log('window: ', window);
window.addEventListener('scroll', (e) => {
console.log('srcoll: ', e)
})
},[])
missing generateStaticParams()
error事情發生在開發 Next.js APP,實作 dynamic routing 時(如:server/[evo]/page.tsx
),會顯示以下錯誤:
上述錯誤訊息中的"output: export"
,是 Next.js 提供支援 Static Exports(靜態導出),透過在設定檔 next.config.js
加上參數:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA)
export default nextConfig
如此一來,即可在 next build
建置時實現 SPA (single-page application),將會在 out
資料夾底下生成靜態檔案,將每個路由分解為單獨的 HTML 檔案,避免在 Client 端載入不必要的 JS 程式碼,減少 bundle 大小以提高頁面效能。
但在 Next.js 若想要實現 Dynamic Routes(動態路由),必須加上 generateStaticParams()
方法,這段 function 只能在 Server Component 執行,在 Client Component 並不支援。
官方文件(Deploying: Static Exports | Next.js) 也提到 App Routing 若想要 Dynamic Routes 必須搭配「只能在 SSR 運行的 generateStaticParams()
」:
如果想要輸出 SPA,又希望能在 'use client'
情境中實現動態路由,則需要將元件拆成兩個部分實作:
generateStaticParams()
方法,以實現動態路由以下是範例程式碼,詳細可參考這篇文章《usage of generateStaticParams with use client | by Vivi - Medium》
server/[evo]/page.tsx
:在 Server Component 引入 generateStaticParams()
方法,以實現動態路由
// server/[evo]/page.tsx
import EvoPage, { Props } from ".";
export function generateStaticParams() {
return [
{ evo: 'test' },
{ evo: 'stage' },
{ evo: 'public' }
];
}
export default function ServerEvoPage({ params }: Props) {
console.log('[ServerEvoPage] params', params)
return <EvoPage params={{ evo: params.evo }}/>;
}
server/[evo]/index.tsx
:在上述 Server Component 引入 Client Component,即可使用 Hooks:
// server/[evo]/index.tsx
'use client';
import React from 'react';
export type Props = {
params: { evo: string};
};
export default function EvoPage({ params }: Props) {
console.log('[EvoPage] params: ', params);
const [data, setData] = React.useState('');
return (
<div>
This is Client Component.
</div>
);
}
or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing