T-CREATOR

Next.js 14時代のESLint徹底解説:Server/Client ComponentsとApp Router対応の最適ルール設定

Next.js 14時代のESLint徹底解説:Server/Client ComponentsとApp Router対応の最適ルール設定

Next.js でアプリケーションを開発していると、「Server Components と Client Components の使い分けで ESLint の警告が出る」「App Router の新機能を使っているのに適切な検証ができていない」といった課題に直面することはありませんか?

Next.js は React のフレームワークとして独自の機能や制約を持っているため、一般的な React の ESLint 設定だけでは不十分な場合があります。特に Next.js 14 以降では、Server Components や App Router などの新機能が導入され、従来の設定では対応しきれない部分が出てきました。

本記事では、Next.js 14 の最新機能に対応した ESLint 設定方法と、実際の開発で遭遇する注意点について詳しく解説いたします。モダンな Next.js 開発に最適化された ESLint 環境を構築していきましょう。

Next.js 14 対応の ESLint 設定

Next.js 14 では多くの新機能が導入されており、これらに対応した ESLint 設定が必要になっています。従来の設定を見直し、最新機能に最適化していきましょう。

Server Components でのルール設定

Next.js 14 の Server Components は、サーバーサイドで実行されるコンポーネントとして多くの制約があります。これらの制約を ESLint で適切にチェックする設定を見ていきましょう。

基本的な Server Components 用設定

javascript// .eslintrc.js
module.exports = {
  extends: [
    'next/core-web-vitals',
    '@typescript-eslint/recommended',
  ],
  rules: {
    // Server Components用のルール
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          {
            group: ['react-dom/client'],
            message:
              'react-dom/client は Server Components では使用できません',
          },
        ],
      },
    ],
    // useStateやuseEffectの誤用を防ぐ
    'react-hooks/rules-of-hooks': 'off', // Server Componentsでは無効化
    '@typescript-eslint/no-floating-promises': 'error',
  },
  overrides: [
    {
      // Server Components用の設定
      files: ['app/**/*.tsx', 'app/**/*.ts'],
      excludedFiles: ['**/*client.tsx', '**/*client.ts'],
      rules: {
        // Server Componentsで使用禁止のAPI
        'no-restricted-globals': [
          'error',
          'window',
          'document',
          'localStorage',
          'sessionStorage',
          'navigator',
        ],
      },
    },
  ],
};

Server Components 固有の検証

typescript// 問題のあるServer Component例
// app/components/BadServerComponent.tsx
export default function BadServerComponent() {
  // ESLintが警告: Server ComponentsではuseStateは使用不可
  const [count, setCount] = useState(0);

  // ESLintが警告: windowオブジェクトはサーバーで利用不可
  const width = window.innerWidth;

  return <div>Bad Server Component</div>;
}

// 正しいServer Component例
// app/components/GoodServerComponent.tsx
interface Props {
  initialData: any;
}

export default async function GoodServerComponent({
  initialData,
}: Props) {
  // Server Componentsではasync/awaitが使用可能
  const data = await fetch('/api/data');
  const result = await data.json();

  return (
    <div>
      <h1>Server Component</h1>
      <pre>{JSON.stringify(result, null, 2)}</pre>
    </div>
  );
}

Client Components 用の設定

javascript// Client Components用の個別設定
{
  files: ['**/*client.tsx', '**/*client.ts', 'components/ui/**/*.tsx'],
  rules: {
    // Client ComponentsではReact Hooksが使用可能
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',

    // Client Component固有の制約
    'no-restricted-syntax': [
      'error',
      {
        selector: 'ImportDeclaration[source.value="fs"]',
        message: 'Client Components では Node.js の fs モジュールは使用できません'
      }
    ]
  }
}

Turbopack との互換性

Next.js 14 では新しいバンドラー Turbopack が導入されました。Turbopack との互換性を保ちながら ESLint を実行するための設定を確認しましょう。

Turbopack 対応の設定調整

javascript// next.config.js
const nextConfig = {
  experimental: {
    turbo: {
      rules: {
        // TurbopackでもESLintを有効にする
        '*.{js,jsx,ts,tsx}': {
          loaders: ['@next/eslint-loader'],
        },
      },
    },
  },
  eslint: {
    // ビルド時のESLintを有効化
    ignoreDuringBuilds: false,
    dirs: ['app', 'pages', 'components', 'lib'],
  },
};

module.exports = nextConfig;

package.json でのスクリプト設定

json{
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build",
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "lint:turbo": "next lint --max-warnings 0 --quiet",
    "type-check": "tsc --noEmit"
  }
}

Turbopack 使用時の注意点

#項目従来の WebpackTurbopack
1ESLint 実行速度標準約 10 倍高速
2Hot Reload標準瞬時更新
3メモリ使用量高め最適化済み
4設定ファイルwebpack.config.jsturbo.json

App Router の新機能対応

App Router は多くの新しいファイル規約と機能を導入しています。これらに対応した ESLint 設定を構築しましょう。

ファイル規約に基づく設定

javascript// App Router固有のファイル規約対応
module.exports = {
  overrides: [
    {
      // layout.tsx用の設定
      files: ['**/layout.tsx'],
      rules: {
        'react/jsx-no-leaked-render': 'error',
        '@typescript-eslint/explicit-function-return-type':
          [
            'error',
            {
              allowedNames: ['RootLayout', 'Layout'],
            },
          ],
      },
    },
    {
      // page.tsx用の設定
      files: ['**/page.tsx'],
      rules: {
        'react/function-component-definition': [
          'error',
          {
            namedComponents: 'arrow-function',
            unnamedComponents: 'arrow-function',
          },
        ],
      },
    },
    {
      // loading.tsx, error.tsx用の設定
      files: ['**/loading.tsx', '**/error.tsx'],
      rules: {
        'react/display-name': 'off',
        '@typescript-eslint/explicit-module-boundary-types':
          'off',
      },
    },
  ],
};

動的ルーティングの検証

typescript// app/blog/[slug]/page.tsx の例
interface PageProps {
  params: { slug: string };
  searchParams: {
    [key: string]: string | string[] | undefined;
  };
}

// ESLintが適切な型定義を強制
export default function BlogPost({
  params,
  searchParams,
}: PageProps) {
  // パラメータの型安全性を確保
  const { slug } = params;

  return (
    <article>
      <h1>ブログ記事: {slug}</h1>
    </article>
  );
}

// 適切なメタデータエクスポート
export async function generateMetadata({
  params,
}: PageProps) {
  return {
    title: `ブログ記事: ${params.slug}`,
  };
}

Route Handlers の設定

javascript// API Routes (app/api/**/route.ts) 用の設定
{
  files: ['app/api/**/route.ts'],
  rules: {
    // HTTP メソッドの適切な実装を強制
    'no-restricted-exports': [
      'error',
      {
        restrictedNamedExports: [
          {
            name: 'default',
            message: 'API Routes では default export は使用できません'
          }
        ]
      }
    ],
    // 適切なレスポンスタイプを強制
    '@typescript-eslint/explicit-function-return-type': [
      'error',
      {
        allowedNames: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
      }
    ]
  }
}

モダン React 開発との統合

Next.js 14 では、React 18 の新機能とさらに進化したパターンが使用できます。これらの機能に対応した ESLint 設定を構築していきましょう。

React Server Components の Lint

React Server Components は、React 18 で導入された新しいアーキテクチャです。Next.js 14 ではこれが標準となっているため、適切な Lint 設定が重要です。

Server Components 固有のルール設定

javascript// React Server Components用の詳細設定
module.exports = {
  extends: [
    'next/core-web-vitals',
    'plugin:react/recommended',
  ],
  plugins: ['react-server-components'],
  rules: {
    // Server Components特有のルール
    'react-server-components/use-client': 'error',
    'react-server-components/use-server': 'error',
    'react-server-components/no-client-hooks': 'error',

    // async Server Componentsの検証
    'react/function-component-definition': [
      'error',
      {
        namedComponents: [
          'function-declaration',
          'arrow-function',
        ],
        unnamedComponents: 'arrow-function',
      },
    ],
  },
};

境界の明確化

typescript// Client Component の適切な宣言
'use client';

import { useState, useEffect } from 'react';

export default function ClientCounter() {
  const [count, setCount] = useState(0);

  // Client Components では React Hooks が使用可能
  useEffect(() => {
    console.log('Counter mounted');
  }, []);

  return (
    <button onClick={() => setCount((c) => c + 1)}>
      Count: {count}
    </button>
  );
}
typescript// Server Component (async function)
import { ClientCounter } from './ClientCounter';

export default async function ServerPage() {
  // Server Component では async/await が使用可能
  const data = await fetch('https://api.example.com/data');
  const result = await data.json();

  return (
    <div>
      <h1>Server Component</h1>
      <p>Data: {result.message}</p>
      {/* Client Component を Server Component から使用 */}
      <ClientCounter />
    </div>
  );
}

Suspense パターンの検証

React 18 の Suspense パターンは、非同期コンポーネントの読み込み状態を管理する強力な機能です。適切な使用方法を ESLint で検証しましょう。

Suspense 境界の検証ルール

javascript// Suspense関連のルール設定
{
  rules: {
    // Suspense境界の適切な配置を検証
    'react/jsx-no-leaked-render': [
      'error',
      {
        validStrategies: ['ternary', 'logical-and']
      }
    ],

    // ErrorBoundaryとの組み合わせを推奨
    'react/require-default-props': 'off', // Suspenseでは不要

    // 適切なfallback componentの指定を強制
    'react/jsx-props-no-spreading': [
      'error',
      {
        exceptions: ['Suspense']
      }
    ]
  }
}

実践的な Suspense 使用例

typescript// app/blog/page.tsx
import { Suspense } from 'react';
import { BlogList } from './BlogList';
import { BlogSkeleton } from './BlogSkeleton';

export default function BlogPage() {
  return (
    <div>
      <h1>ブログ一覧</h1>
      <Suspense fallback={<BlogSkeleton />}>
        <BlogList />
      </Suspense>
    </div>
  );
}

// app/blog/BlogList.tsx (Server Component)
export async function BlogList() {
  // ESLint: async Server Component として適切
  const posts = await fetch('/api/posts');
  const data = await posts.json();

  return (
    <ul>
      {data.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

useOptimistic との相性

React 18.3 で導入された useOptimistic フックは、楽観的 UI アップデートを実現するための新機能です。このフックの適切な使用方法を ESLint で検証します。

useOptimistic 使用時のルール

javascript// useOptimistic専用のルール設定
{
  rules: {
    // useOptimisticの適切な使用パターンを強制
    'react-hooks/exhaustive-deps': [
      'warn',
      {
        additionalHooks: '(useOptimistic)'
      }
    ],

    // Server Actionsとの組み合わせを検証
    'no-restricted-syntax': [
      'error',
      {
        selector: 'CallExpression[callee.name="useOptimistic"][arguments.length!=2]',
        message: 'useOptimistic には2つの引数が必要です'
      }
    ]
  }
}

useOptimistic の実装例

typescript'use client';

import { useOptimistic } from 'react';
import { submitComment } from './actions';

interface Comment {
  id: string;
  text: string;
  isPending?: boolean;
}

export function CommentForm({
  comments,
}: {
  comments: Comment[];
}) {
  const [optimisticComments, addOptimisticComment] =
    useOptimistic(comments, (state, newComment: string) => [
      ...state,
      {
        id: Date.now().toString(),
        text: newComment,
        isPending: true,
      },
    ]);

  const handleSubmit = async (formData: FormData) => {
    const comment = formData.get('comment') as string;

    // 楽観的アップデート
    addOptimisticComment(comment);

    // Server Action の実行
    await submitComment(comment);
  };

  return (
    <form action={handleSubmit}>
      <input name='comment' placeholder='コメントを入力' />
      <button type='submit'>送信</button>

      <ul>
        {optimisticComments.map((comment) => (
          <li
            key={comment.id}
            style={{ opacity: comment.isPending ? 0.5 : 1 }}
          >
            {comment.text}
          </li>
        ))}
      </ul>
    </form>
  );
}

Server Actions との連携

typescript// app/actions.ts
'use server';

export async function submitComment(comment: string) {
  // Server Action の適切な実装
  await new Promise((resolve) => setTimeout(resolve, 1000));

  // データベースへの保存処理
  console.log('Comment saved:', comment);

  // revalidateTag や redirect などの Next.js 機能
  revalidatePath('/comments');
}

この設定により、Next.js 14 の最新機能を使用しながら、コードの品質を保つことができます。Server Components と Client Components の適切な分離、Suspense パターンの正しい実装、そして新しい React フックの効果的な活用が可能になるでしょう。

パフォーマンス重視の設定調整

Next.js アプリケーションにおいて、ESLint の設定はパフォーマンスにも大きく影響します。効率的で高速な開発体験を実現するための設定調整を詳しく見ていきましょう。

Bundle analyzer との連携

Next.js では @next​/​bundle-analyzer を使用してバンドルサイズを分析できます。ESLint と連携することで、パフォーマンスに影響する問題を早期に発見できます。

Bundle 分析と連携した ESLint 設定

javascript// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')(
  {
    enabled: process.env.ANALYZE === 'true',
  }
);

const nextConfig = {
  eslint: {
    // Bundle分析時は警告レベルを下げる
    ignoreDuringBuilds: process.env.ANALYZE === 'true',
  },

  experimental: {
    // ESLintのパフォーマンス最適化
    esmExternals: true,
    serverComponentsExternalPackages: ['@prisma/client'],
  },
};

module.exports = withBundleAnalyzer(nextConfig);

パフォーマンス関連の ESLint ルール

javascript// .eslintrc.js
module.exports = {
  rules: {
    // 大きなライブラリの不適切なインポートを防ぐ
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          {
            group: ['lodash'],
            message:
              'lodash 全体のインポートではなく、必要な関数のみをインポートしてください',
          },
          {
            group: ['moment'],
            message:
              'moment.js の代わりに date-fns や dayjs を使用してください',
          },
        ],
      },
    ],

    // 動的インポートの推奨
    'import/dynamic-import-chunkname': 'warn',

    // 未使用のCSS-in-JS スタイルを検出
    'no-unused-vars': [
      'error',
      {
        varsIgnorePattern: '^(styled|css|keyframes)',
      },
    ],
  },
};

package.json での分析スクリプト

json{
  "scripts": {
    "analyze": "ANALYZE=true yarn build",
    "lint:performance": "next lint --config .eslintrc.performance.js",
    "lint:bundle": "yarn analyze && yarn lint:performance"
  }
}

Code splitting の最適化チェック

適切な Code splitting は、アプリケーションの初期読み込み時間を大幅に改善します。ESLint で Code splitting の最適化をチェックしましょう。

Dynamic import の適切な使用

javascript// Code splitting関連のルール
{
  rules: {
    // 大きなコンポーネントの動的インポートを推奨
    'import/no-default-export': [
      'error',
      {
        allow: ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx']
      }
    ],

    // 条件付きインポートの最適化
    'no-restricted-syntax': [
      'error',
      {
        selector: 'ImportDeclaration[source.value=/^(?!.*\\.(css|scss|sass)$).*$/] CallExpression[callee.name="import"][arguments.0.type="Literal"]',
        message: '動的インポートには適切なchunkNameを指定してください'
      }
    ]
  }
}

実装例

typescript// 適切な動的インポート
import dynamic from 'next/dynamic';

// 重いコンポーネントの遅延ロード
const HeavyChart = dynamic(
  () => import('@/components/HeavyChart'),
  {
    loading: () => <div>チャートを読み込み中...</div>,
    ssr: false, // クライアントサイドでのみ実行
  }
);

// 条件付きコンポーネントの動的ロード
const AdminPanel = dynamic(
  () => import('@/components/AdminPanel'),
  {
    loading: () => <div>管理パネルを読み込み中...</div>,
  }
);

export default function Dashboard({
  isAdmin,
}: {
  isAdmin: boolean;
}) {
  return (
    <div>
      <h1>ダッシュボード</h1>
      <HeavyChart data={chartData} />
      {isAdmin && <AdminPanel />}
    </div>
  );
}

Image 最適化の Lint 設定

Next.js の Image コンポーネントは強力な最適化機能を提供しますが、適切に使用されていない場合があります。ESLint でこれらの使用法をチェックしましょう。

Image 最適化のルール設定

javascript// Image最適化関連のルール
{
  rules: {
    // next/image の使用を強制
    'no-restricted-imports': [
      'error',
      {
        paths: [
          {
            name: 'img',
            message: 'HTMLのimg要素ではなく、next/imageのImageコンポーネントを使用してください'
          }
        ]
      }
    ],

    // Image コンポーネントの適切な props 指定
    'jsx-a11y/alt-text': [
      'error',
      {
        elements: ['img', 'Image'],
        img: ['Image']
      }
    ]
  }
}

最適化された Image 使用例

typescriptimport Image from 'next/image';

// 適切なImage使用例
export function OptimizedImageGallery() {
  return (
    <div className='grid grid-cols-3 gap-4'>
      {images.map((image, index) => (
        <Image
          key={image.id}
          src={image.src}
          alt={image.description} // alt属性必須
          width={300}
          height={200}
          priority={index < 3} // Above the fold画像に優先度設定
          placeholder='blur' // ぼかしプレースホルダー
          blurDataURL='data:image/jpeg;base64,...' // Base64エンコードされたプレースホルダー
          className='rounded-lg'
        />
      ))}
    </div>
  );
}

// 問題のある使用例(ESLintが警告)
export function BadImageUsage() {
  return (
    <div>
      {/* ESLint警告: next/imageを使用すべき */}
      <img src='/image.jpg' />

      {/* ESLint警告: alt属性が必要 */}
      <Image src='/image.jpg' width={300} height={200} />

      {/* ESLint警告: 適切なサイズ指定が必要 */}
      <Image src='/image.jpg' alt='画像' />
    </div>
  );
}

新しい開発パターンへの対応

Next.js 14 では多くの新しい開発パターンが導入されています。これらの機能に対応した ESLint 設定を構築しましょう。

Partial Prerendering での注意点

Partial Prerendering(PPR)は、Next.js 14 で実験的に導入された機能で、静的部分と動的部分を組み合わせてレンダリングします。

PPR 対応の ESLint 設定

javascript// PPR用の設定
{
  rules: {
    // Suspense境界の適切な配置を強制
    'react/jsx-no-leaked-render': 'error',

    // 静的・動的コンテンツの明確な分離
    'no-restricted-syntax': [
      'error',
      {
        selector: 'JSXElement[openingElement.name.name="Suspense"] JSXElement[openingElement.name.name="Suspense"]',
        message: 'ネストしたSuspenseは避けてください。PPRでは単一レベルでの使用を推奨します'
      }
    ]
  }
}

PPR 対応コンポーネント例

typescript// app/product/[id]/page.tsx
import { Suspense } from 'react';

export default function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div>
      {/* 静的部分:即座にレンダリング */}
      <header>
        <h1>商品詳細</h1>
        <nav>ナビゲーション</nav>
      </header>

      {/* 動的部分:Suspenseで包む */}
      <main>
        <Suspense fallback={<ProductSkeleton />}>
          <ProductDetails id={params.id} />
        </Suspense>

        <Suspense fallback={<ReviewsSkeleton />}>
          <ProductReviews id={params.id} />
        </Suspense>
      </main>

      {/* 静的部分:フッター */}
      <footer>Copyright 2024</footer>
    </div>
  );
}

Parallel Routes の設定

Parallel Routes は、同じレイアウト内で複数のページを同時に表示する機能です。この機能に対応した ESLint 設定を見ていきましょう。

Parallel Routes 用のファイル構造検証

javascript// ファイル命名規則の検証
{
  rules: {
    'file-naming-convention': [
      'error',
      {
        patterns: [
          {
            files: '**/@*/**',
            naming: 'kebab-case',
            message: 'Parallel Routesのスロット名はkebab-caseにしてください'
          }
        ]
      }
    ]
  }
}

Parallel Routes 実装例

typescript// app/dashboard/@analytics/page.tsx
export default function AnalyticsSlot() {
  return (
    <div className='analytics-panel'>
      <h2>アナリティクス</h2>
      {/* アナリティクス関連のコンテンツ */}
    </div>
  );
}

// app/dashboard/@notifications/page.tsx
export default function NotificationsSlot() {
  return (
    <div className='notifications-panel'>
      <h2>通知</h2>
      {/* 通知関連のコンテンツ */}
    </div>
  );
}

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  analytics,
  notifications,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  notifications: React.ReactNode;
}) {
  return (
    <div className='dashboard-layout'>
      <main>{children}</main>
      <aside className='sidebar'>
        {analytics}
        {notifications}
      </aside>
    </div>
  );
}

Intercepting Routes の検証

Intercepting Routes は、特定の条件下でルートをインターセプトし、異なるコンテンツを表示する機能です。

Intercepting Routes 用の設定

javascript// Intercepting Routes用のルール
{
  rules: {
    // インターセプトルートの適切な命名
    'filename-convention': [
      'error',
      {
        pattern: '^(.*)\\((.*)\\)(.*)$',
        message: 'Intercepting Routesは (..) または (.) パターンを使用してください'
      }
    ],

    // モーダルコンポーネントの適切な実装
    'react/jsx-no-leaked-render': 'error'
  }
}

実装例

typescript// app/photos/(..)modal/[id]/page.tsx
import { Modal } from '@/components/Modal';

export default function PhotoModal({
  params,
}: {
  params: { id: string };
}) {
  return (
    <Modal>
      <img
        src={`/photos/${params.id}.jpg`}
        alt={`Photo ${params.id}`}
        className='max-w-full max-h-full'
      />
    </Modal>
  );
}

// app/photos/[id]/page.tsx
export default function PhotoPage({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div className='photo-page'>
      <img
        src={`/photos/${params.id}.jpg`}
        alt={`Photo ${params.id}`}
        className='w-full'
      />
    </div>
  );
}

将来への備えとスケーラビリティ

長期的なメンテナンスと拡張性を考慮した ESLint 設定の構築方法を解説します。

設定のモジュール化

大規模なプロジェクトでは、ESLint 設定を適切にモジュール化することが重要です。

設定ファイルの分割

javascript// eslint-configs/base.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
};

// eslint-configs/nextjs.js
module.exports = {
  extends: ['./base.js', 'next/core-web-vitals'],
  rules: {
    '@next/next/no-img-element': 'error',
    '@next/next/no-unwanted-polyfillio': 'error',
  },
};

// eslint-configs/react.js
module.exports = {
  extends: [
    './base.js',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  rules: {
    'react/react-in-jsx-scope': 'off',
    'react-hooks/exhaustive-deps': 'warn',
  },
};

プロジェクト固有の設定統合

javascript// .eslintrc.js
module.exports = {
  extends: [
    './eslint-configs/nextjs.js',
    './eslint-configs/react.js',
  ],
  overrides: [
    {
      files: ['app/**/*.tsx', 'app/**/*.ts'],
      extends: ['./eslint-configs/app-router.js'],
    },
    {
      files: ['pages/**/*.tsx', 'pages/**/*.ts'],
      extends: ['./eslint-configs/pages-router.js'],
    },
  ],
};

プラグインエコシステムの活用

Next.js 開発を支援する有用な ESLint プラグインの活用方法を紹介します。

推奨プラグイン一覧

#プラグイン名用途重要度
1@next​/​eslint-plugin-nextNext.js 固有のルール★★★
2eslint-plugin-react-hooksHooks 使用時のルール★★★
3eslint-plugin-jsx-a11yアクセシビリティ★★☆
4eslint-plugin-importimport/export 最適化★★☆
5eslint-plugin-securityセキュリティチェック★☆☆

プラグインの段階的導入

javascript// 段階的なプラグイン導入設定
module.exports = {
  extends: [
    // Phase 1: 基本設定
    'next/core-web-vitals',

    // Phase 2: 品質向上
    'plugin:jsx-a11y/recommended',
    'plugin:import/recommended',

    // Phase 3: セキュリティ強化
    'plugin:security/recommended',
  ],

  // 段階的なルール有効化
  rules: {
    // 即座に有効化
    '@next/next/no-img-element': 'error',

    // 警告から開始
    'jsx-a11y/alt-text': 'warn',
    'import/order': 'warn',

    // 将来的に有効化予定
    'security/detect-object-injection': 'off', // TODO: Phase 3で有効化
  },
};

継続的な設定メンテナンス

ESLint 設定の継続的な改善とメンテナンス方法を説明します。

自動化されたメンテナンス

json{
  "scripts": {
    "lint:audit": "eslint --print-config . | jq '.rules' > eslint-rules-audit.json",
    "lint:unused": "eslint-find-rules --unused .eslintrc.js",
    "lint:deprecated": "eslint-find-rules --deprecated .eslintrc.js",
    "lint:upgrade": "yarn upgrade-interactive --latest"
  }
}

設定品質の測定

javascript// eslint-config-quality-check.js
const fs = require('fs');
const path = require('path');

function analyzeESLintConfig() {
  const config = require('./.eslintrc.js');

  const metrics = {
    totalRules: 0,
    errorRules: 0,
    warnRules: 0,
    customRules: 0,
    coverage: 0,
  };

  // ルール数の計算
  Object.entries(config.rules || {}).forEach(
    ([rule, setting]) => {
      metrics.totalRules++;

      if (
        setting === 'error' ||
        (Array.isArray(setting) && setting[0] === 'error')
      ) {
        metrics.errorRules++;
      } else if (
        setting === 'warn' ||
        (Array.isArray(setting) && setting[0] === 'warn')
      ) {
        metrics.warnRules++;
      }
    }
  );

  console.log('ESLint設定品質レポート:', metrics);
}

analyzeESLintConfig();

実際のプロジェクト適用例

具体的なプロジェクトタイプ別の最適な ESLint 設定例を紹介します。

E-commerce サイトでの設定例

E-commerce サイトでは、パフォーマンス、セキュリティ、アクセシビリティが特に重要です。

javascript// E-commerce向けESLint設定
module.exports = {
  extends: [
    'next/core-web-vitals',
    'plugin:jsx-a11y/recommended',
    'plugin:security/recommended',
  ],
  rules: {
    // パフォーマンス重視
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          'lodash', // Tree-shakingが効かない
          'moment', // サイズが大きい
        ],
      },
    ],

    // セキュリティ強化
    'security/detect-object-injection': 'error',
    'security/detect-non-literal-fs-filename': 'error',

    // アクセシビリティ
    'jsx-a11y/alt-text': 'error',
    'jsx-a11y/aria-label': 'error',
    'jsx-a11y/keyboard-navigation': 'error',
  },
};

ダッシュボードアプリケーション向け

データ可視化やユーザー管理を含むダッシュボードアプリケーション向けの設定です。

javascript// ダッシュボード向けESLint設定
module.exports = {
  extends: [
    'next/core-web-vitals',
    'plugin:react-hooks/recommended',
  ],
  rules: {
    // データフェッチング最適化
    'react-hooks/exhaustive-deps': [
      'error',
      {
        additionalHooks:
          '(useSWR|useQuery|useInfiniteQuery)',
      },
    ],

    // 大量データ表示対応
    'react/jsx-key': 'error',
    'react/no-array-index-key': 'warn',

    // リアルタイム更新対応
    'no-restricted-globals': [
      'error',
      'setInterval', // useEffectでの適切な管理を強制
      'setTimeout',
    ],
  },
};

ブログ・CMS サイトでの最適化

コンテンツ重視のサイトでの SEO とパフォーマンス最適化設定です。

javascript// ブログ・CMS向けESLint設定
module.exports = {
  extends: ['next/core-web-vitals'],
  rules: {
    // SEO最適化
    '@next/next/no-img-element': 'error', // next/imageの使用を強制
    '@next/next/no-page-custom-font': 'warn', // フォント最適化

    // コンテンツ配信最適化
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          {
            group: ['react-markdown'],
            importNames: ['default'],
            message:
              '動的インポートを使用してreact-markdownを読み込んでください',
          },
        ],
      },
    ],

    // メタデータ最適化
    'jsx-a11y/html-has-lang': 'error',
    'jsx-a11y/page-has-lang': 'error',
  },
};

まとめ

Next.js 14 での ESLint 設定は、従来の React アプリケーションとは異なる多くの考慮事項があります。本記事では、最新機能に対応した実践的な設定方法を詳しく解説いたしました。

重要なポイント

  • Server Components 対応: 適切なルール設定で SSR の制約を管理
  • App Router 活用: 新しいファイル規約とルーティング機能への対応
  • パフォーマンス最適化: Bundle 分析と Code splitting の効率化
  • 新機能対応: PPR、Parallel Routes、Intercepting Routes への対応
  • スケーラビリティ: モジュール化された設定とメンテナンス戦略

導入時の推奨アプローチ

  1. 段階的導入: 基本設定から始めて徐々に厳格化
  2. プロジェクト特性の考慮: E-commerce、ダッシュボード、CMS など用途に応じた最適化
  3. 継続的改善: 定期的な設定見直しとアップデート

Next.js 14 の新機能を活用しながら、適切な ESLint 設定により高品質なアプリケーション開発を実現できるでしょう。特に Server Components と Client Components の適切な分離、最新の React パターンの活用、そして将来の拡張性を考慮した設定により、長期的に保守しやすいコードベースを構築することができます。

関連リンク