Next.js の _app.js とは?役割・使い方・よくあるエラーを完全解説

React / Next.js

Next.js の _app.js とは?役割・使い方・よくあるエラーを完全解説

Next.jsを使用していると、プロジェクトのpagesディレクトリに_app.jsというファイルが自動生成されます。このファイルが何か、どのような役割を果たしているのか、初心者にとっては理解しづらいかもしれません。本記事では、Next.jsの_app.jsについて、その役割、使い方、そしてよくあるエラーの対処方法を詳しく解説します。

Next.js _app.js の原因の説明

_app.js とは何か

_app.jsは、Next.jsアプリケーション全体のエントリーポイント(入口)となる特別なファイルです。アンダースコアで始まる_app.jsという名前は、Next.jsが特別なファイルとして認識するためのルールで、このファイルは通常のページファイルのようにURLとしてアクセスされません。

_app.js の役割

_app.jsの主な役割は以下の通りです:

  • グローバルレイアウト:すべてのページに共通するレイアウトやコンポーネントを定義
  • グローバルスタイル:CSSやTailwindCSSなどのグローバルスタイルを適用
  • 状態管理:Redux、Zustand、Context APIなどの状態管理プロバイダーをラップ
  • ページ間の永続化:ページ遷移時に保持されるべき状態やデータを管理
  • カスタムロジック:ページレンダリング前に実行する共通処理

なぜ _app.js が必要なのか

通常のReactアプリケーションでは、App.jsが全体の親コンポーネントとなります。Next.jsでは、ファイルベースのルーティングシステムを採用しているため、複数のページファイル(index.jsabout.jsなど)が存在します。これらのページすべてを共通のレイアウトやスタイルでラップするために、_app.jsが必要となるのです。

解決手順

ステップ1:_app.js ファイルの確認

まず、プロジェクトのpagesディレクトリに_app.jsが存在するか確認してください。Next.jsでcreate-next-appを使用して新規プロジェクトを作成した場合、自動的に生成されています。

ステップ2:_app.js の基本構造を理解する

_app.jsは、ComponentpagePropsという2つのプロップを受け取る関数コンポーネントです。Componentはレンダリング対象のページコンポーネント、pagePropsはそのページに渡すプロップです。

ステップ3:グローバルスタイルの設定

CSSファイルをインポートする場合、_app.js内でのみグローバルCSSをインポート可能です。他のファイルでグローバルCSSをインポートするとエラーが発生します。

ステップ4:プロバイダーのラップ

状態管理ライブラリを使用する場合、プロバイダーでComponentをラップします。これにより、すべてのページから状態にアクセス可能になります。

ステップ5:カスタムロジックの追加

useEffectuseRouterを使用して、ページ遷移時に実行される処理を記述できます。

コード例

基本的な _app.js の例

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

これは最もシンプルな_app.jsです。グローバルCSSをインポートし、すべてのページコンポーネントをレンダリングします。

グローバルスタイルを追加した例

import '../styles/globals.css'
import '../styles/variables.css'

function MyApp({ Component, pageProps }) {
  return (
    <div className=\"app-wrapper\">
      <Component {...pageProps} />
    </div>
  )
}

export default MyApp

レイアウトコンポーネントを使用した例

import '../styles/globals.css'
import Layout from '../components/Layout'

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

export default MyApp

Layoutコンポーネントは、ヘッダー、フッター、ナビゲーションバーなど、すべてのページに共通する要素を含む親コンポーネントです。

状態管理(Redux)を使用した例

import '../styles/globals.css'
import { Provider } from 'react-redux'
import store from '../store'
import Layout from '../components/Layout'

function MyApp({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </Provider>
  )
}

export default MyApp

ページ遷移時の処理を含む例

import '../styles/globals.css'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import Layout from '../components/Layout'

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = (url) => {
      console.log(`ページが ${url} に遷移しました`)
      // ページ遷移時の処理
      window.scrollTo(0, 0)
    }

    router.events.on('routeChangeStart', handleRouteChange)
    
    return () => {
      router.events.off('routeChangeStart', handleRouteChange)
    }
  }, [router.events])

  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

export default MyApp

Context APIを使用した例

import '../styles/globals.css'
import { ThemeProvider } from '../context/ThemeContext'
import Layout from '../components/Layout'

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </ThemeProvider>
  )
}

export default MyApp

複数のプロバイダーをネストした例

import '../styles/globals.css'
import { Provider } from 'react-redux'
import { SessionProvider } from 'next-auth/react'
import store from '../store'
import Layout from '../components/Layout'

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
      <Provider store={store}>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </Provider>
    </SessionProvider>
  )
}

export default MyApp

よくある間違い

間違い1:ページファイルでグローバルCSSをインポート

グローバルCSSは_app.jsでのみインポート可能です。他のファイルでグローバルCSSをインポートすると、以下のエラーが発生します:

// ❌ 間違い:pages/about.js でグローバルCSSをインポート
import '../styles/globals.css'

export default function About() {
  return <div>About Page</div>
}

エラーメッセージ:Global CSS cannot be imported from files other than your Custom _app.js. Due to the Global nature of stylesheets, and to avoid conflicts, please import your global stylesheet only in custom _app.js.

解決方法:

// ✅ 正しい:_app.js でのみグローバルCSSをインポート
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

間違い2:_app.js に getStaticProps や getServerSideProps を記述

_app.jsはページではなく、アプリケーション全体のコンポーネントのため、データ取得関数を記述できません。

// ❌ 間違い
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export async function getServerSideProps() {
  // これは動作しません
  return { props: {} }
}

export default MyApp

各ページで必要なデータは、ページ自体でgetStaticPropsまたはgetServerSidePropsを使用して取得してください。

間違い3:_app.js を pages ディレクトリ以外に配置

_app.jsは必ずpagesディレクトリのルートに配置する必要があります。サブディレクトリに配置しても機能しません。

プロジェクト構成

project/
├── pages/
│   ├── _app.js          ✅ 正しい位置
│   ├── index.js
│   └── about.js
│
└── ❌ pages/admin/_app.js (これは機能しません)

間違い4:プロバイダーの順序を誤る

複数のプロバイダーを使用する場合、その順序が重要です。依存関係を考慮して正しい順序で配置してください。

// ❌ 間違い:順序が不適切
function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Provider store={store}>
        <ThemeProvider>
          <Component {...pageProps} />
        </ThemeProvider>
      </Provider>
    </Layout>
  )
}

// ✅ 正しい:依存関係を考慮した順序
function MyApp({ Component, pageProps }) {
  return (
    <Provider store={store}>
      <ThemeProvider>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ThemeProvider>
    </Provider>
  )
}

間違い5:_document.js の役割を _app.js と混同

Next.jsには_app.js以外に_document.jsもあります。これらは異なる役割を持ちます:

  • _app.js:各ページコンポーネントの親コンポーネント。React のライフサイクルで動作
  • _document.js:HTML ドキュメント全体の構造を制御。<html><body>タグなどを定義
// _document.js の例
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

間違い6:_app.js で直接 useState を使用する際の注意

_app.jsuseStateを使用する場合、ページ遷移時に状態がリセットされることに注意が必要です。

// ❌ 注意:ページ遷移でリセットされます
import { useState } from 'react'

function MyApp({ Component, pageProps }) {
  const [count, setCount] = useState(0) // ページ遷移でリセット

  return (
    <div>
      <p>Count: {count}</p>
      <Component {...pageProps} />
    </div>
  )
}

export default MyApp

ページ遷移後も状態を保持したい場合は、useRefや状態管理ライブラリを使用してください。

まとめ

Next.jsの_app.jsはアプリケーション全体のエントリーポイントであり、グローバルスタイル、レイアウト、状態管理、そしてカスタムロジックを集中管理するための重要なファイルです。

重要なポイント:

  • _app.jsはアプリケーション全体の親コンポーネントとして機能する
  • グローバルCSSは_app.jsでのみインポート可能
  • 複数のプロバイダーを使用する場合は、正しい順序でネストする
  • _app.js_document.jsの役割を混同しない
  • ページ固有のロジックは各ページファイルに記述する
  • データ取得関数(getStaticProps、getServerSideProps)は_app.jsには記述できない

これらのポイントを理解することで、Next.jsプロジェクトの構造がより明確になり、エラーの予防やデバッグもスムーズになります。_app.jsを適切に活用して、スケーラブルで保守性の高いNext.jsアプリケーションを構築してください。

タイトルとURLをコピーしました