0%

【學習筆記】Next.js 錯誤修復紀錄: 「Window is not defined」、「use client & missing generateStaticParams() error」

前言

這篇主要用來記錄 Next.js 開發過程採的坑,後續也會不定期更新:

  • 問題一:如何修復 "Window is not defined" error
  • 問題二:在 use client 頁面實作動態路由顯示 missing generateStaticParams() error

問題一:如何修復 “Window is not defined” error in Next.js

事情發生在開發 Next.js APP,要實作 Google 登入驗證,需要使用 WindowDocument 物件時,發現程式會報出以下錯誤:

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 是否存在
  • 使用 useEffect hook

(1) 檢查 window 是否存在

判斷 window 是否存在,確定在瀏覽器中才執行指定的程式碼:

const isBrowser = () => typeof window !== 'undefined';

if (typeof window !== 'undefined') {
    // Client-side-only
    console.log('window: ', window);
};

(2) 使用 useEffect 等 Hooks

透過 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)
  })
},[])

參考資料

  • Window is not defined in Next.js React app
  • 如何修复Next.js中的 “window is not defined”? - 掘金

問題二:在 use client 頁面實作動態路由顯示 missing generateStaticParams() error

事情發生在開發 Next.js APP,實作 dynamic routing 時(如:server/[evo]/page.tsx),會顯示以下錯誤:

error

上述錯誤訊息中的"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()」:

unsupported

如何解決?

如果想要輸出 SPA,又希望能在 'use client' 情境中實現動態路由,則需要將元件拆成兩個部分實作:

  • 在 Server Component 引入 generateStaticParams() 方法,以實現動態路由
  • 接著引入 Client Component,即可使用 Hooks

以下是範例程式碼,詳細可參考這篇文章《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>
  );
}

參考資料

  • [NEXT-1049] use client with generateStaticParams will opt out of static generation #46735
  • [NEXT-1030] output: ‘export’ with use client in dynamic routes doesn’t work #48022
  • App Router with output: export does not support useParams() on client #54393
  • NextJs-静态导出
  • Day 19 - Next.js 13 App Router 動態路由 Dynamic Routes & getStaticParams()