跳到主要內容
+

Next.js App Router

了解如何在 Next.js App Router 中使用 MUI Base。

範例

在新 App Router 架構的專案中從頭開始?

透過這個範例:MUI Base - 搭配 TypeScript 和 Tailwind CSS 的 Next.js App Router,直接進入程式碼。

Next.js 和 React 伺服器組件

Next.js App Router 實作了 React 伺服器組件,React 即將推出的功能

為了支援 App Router,MUI Base 中需要存取瀏覽器 API 的組件和 Hook 都使用 "use client" 指令匯出。

在 App Router 中設定 MUI Base

MUI Base 讓您可以自由選擇自己的樣式解決方案,因此設定 Next.js App Router 專案很大程度上取決於您的選擇。本指南涵蓋 Tailwind CSS、Emotion 和其他 CSS-in-JS 解決方案,例如 styled-components。

Tailwind CSS

依照Tailwind CSS 關於搭配 Next.js 使用的指南,並務必將 app 目錄和其他目錄新增至 tailwind.config.js,如下所示

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}'
    // or if not using the `src` directory:
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

請參考這個範例儲存庫,以取得使用 MUI Base 和 Tailwind CSS 的 Next.js 13 應用程式完整運作示範。

Emotion

如果您使用 Emotion,或類似 MUI System 等基於 Emotion 的套件,請建立自訂的 ThemeRegistry 組件,將 Emotion CacheProvider、Material UI ThemeProvidernext/navigation 中的 useServerInsertedHTML Hook 結合在一起,如下所示

// app/ThemeRegistry.tsx
'use client';
import createCache from '@emotion/cache';
import { useServerInsertedHTML } from 'next/navigation';
import { CacheProvider, ThemeProvider } from '@emotion/react';
import theme from '/path/to/your/theme';

// This implementation is from emotion-js
// https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902
export default function ThemeRegistry(props) {
  const { options, children } = props;

  const [{ cache, flush }] = React.useState(() => {
    const cache = createCache(options);
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted: string[] = [];
    cache.insert = (...args) => {
      const serialized = args[1];
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push(serialized.name);
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const names = flush();
    if (names.length === 0) {
      return null;
    }
    let styles = '';
    for (const name of names) {
      styles += cache.inserted[name];
    }
    return (
      <style
        key={cache.key}
        data-emotion={`${cache.key} ${names.join(' ')}`}
        dangerouslySetInnerHTML={{
          __html: styles,
        }}
      />
    );
  });

  return (
    <CacheProvider value={cache}>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </CacheProvider>
  );
}

// app/layout.js
export default function RootLayout(props) {
  return (
    <html lang="en">
      <body>
        <ThemeRegistry options={{ key: 'mui' }}>{props.children}</ThemeRegistry>
      </body>
    </html>
  );
}

如果您需要進一步覆寫主題樣式(例如使用 CSS Modules),Emotion 為 createCache 提供 prepend: true 選項來反轉注入順序,以便自訂樣式可以覆寫主題,而無需使用 !important

目前,prepend 在 App Router 中無法可靠地運作,但您可以透過將 Emotion 樣式包裝在 CSS @layer 中並修改上述程式碼片段來解決此問題

 useServerInsertedHTML(() => {
   const names = flush();
   if (names.length === 0) {
     return null;
   }
   let styles = '';
   for (const name of names) {
     styles += cache.inserted[name];
   }
   return (
     <style
       key={cache.key}
       data-emotion={`${cache.key} ${names.join(' ')}`}
       dangerouslySetInnerHTML={{
-        __html: styles,
+        __html: options.prepend ? `@layer emotion {${styles}}` : styles,
       }}
     />
   );
 });

其他 CSS-in-JS 函式庫

若要將 Next.js 與 MUI Base 和 styled-components 或其他 CSS-in-JS 解決方案搭配使用,請依照 Next.js 關於 CSS-in-JS 的文件

自訂

為插槽屬性使用回呼函數

MUI Base 中常見的自訂方法是將回呼函數傳遞給 slotProps 中的插槽,以便套用動態屬性。例如,您可能想要在按鈕停用時,透過套用不同的類別來變更背景顏色

// page.tsx

export default function Page() {
  return (
    <React.Fragment>
      {/* Next.js won't render this button without 'use-client'*/}
      <Button
        slotProps={{
          root: (ownerState: ButtonOwnerState) => ({
            className: ownerState.disabled ? 'bg-gray-400' : 'bg-blue-400',
          }),
        }}
      >
        Submit
      </Button>

      {/* Next.js can render this */}
      <Button
        slotProps={{
          root: {
            className: 'bg-gray-400',
          },
        }}
      >
        Return
      </Button>
    </React.Fragment>
  );
}

遺憾的是,由於函數屬性是不可序列化的,這在伺服器組件中無法運作。相反地,Next.js 團隊建議將這類組件「向下移動到樹狀結構中」,以避免此問題並提升整體效能。