diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/01-installation.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/01-installation.mdx new file mode 100644 index 00000000..48a526c1 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/01-installation.mdx @@ -0,0 +1,341 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:28.869Z +title: 新しいNext.jsプロジェクトのセットアップ方法 +nav_title: インストール +description: 新しいNext.jsアプリケーションを`create-next-app`で作成する方法。TypeScriptやESLintのセットアップ、`next.config.js`ファイルの設定について説明します。 +--- + +{/* このドキュメントの内容はApp RouterとPages Routerで共有されています。Pages Router固有のコンテンツを追加する場合は、`コンテンツ`コンポーネントを使用してください。共有コンテンツはコンポーネントでラップしないでください。 */} + +## システム要件 + +開始する前に、システムが以下の要件を満たしていることを確認してください: + +- [Node.js 18.18](https://nodejs.org/) 以降 +- macOS、Windows(WSLを含む)、またはLinux + +## 自動インストール + +新しいNext.jsアプリを作成する最も簡単な方法は、[`create-next-app`](/docs/app/api-reference/cli/create-next-app)を使用することです。これにより、すべてが自動的にセットアップされます。プロジェクトを作成するには、以下を実行します: + +```bash filename="ターミナル" +npx create-next-app@latest +``` + +インストール中に以下のプロンプトが表示されます: + +```txt filename="ターミナル" +プロジェクト名を入力してください? my-app +TypeScriptを使用しますか? No / Yes +ESLintを使用しますか? No / Yes +Tailwind CSSを使用しますか? No / Yes +コードを`src/`ディレクトリ内に配置しますか? No / Yes +App Routerを使用しますか? (推奨) No / Yes +`next dev`にTurbopackを使用しますか? No / Yes +インポートエイリアスをカスタマイズしますか? (`@/*`がデフォルト) No / Yes +どのインポートエイリアスを設定しますか? @/* +``` + +プロンプトの後、[`create-next-app`](/docs/app/api-reference/cli/create-next-app)はプロジェクト名のフォルダを作成し、必要な依存関係をインストールします。 + +## 手動インストール + +新しいNext.jsアプリを手動で作成するには、必要なパッケージをインストールします: + +```bash filename="ターミナル" +npm install next@latest react@latest react-dom@latest +``` + +次に、以下のスクリプトを`package.json`ファイルに追加します: + +```json filename="package.json" +{ + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + } +} +``` + +これらのスクリプトは、アプリケーション開発のさまざまな段階を参照しています: + +- `next dev`: 開発サーバーを起動します +- `next build`: 本番用にアプリケーションをビルドします +- `next start`: 本番サーバーを起動します +- `next lint`: ESLintを実行します + + + +### `app`ディレクトリの作成 + +Next.jsはファイルシステムベースのルーティングを使用しており、アプリケーション内のルートはファイルの構造によって決定されます。 + +`app`フォルダを作成します。次に、`app`内に`layout.tsx`ファイルを作成します。このファイルは[ルートレイアウト](/docs/app/api-reference/file-conventions/layout#root-layout)です。必須であり、``タグと``タグを含める必要があります。 + +```tsx filename="app/layout.tsx" switcher +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +初期コンテンツを含むホームページ`app/page.tsx`を作成します: + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +`layout.tsx`と`page.tsx`は、ユーザーがアプリケーションのルート(`/`)にアクセスしたときにレンダリングされます。 + +Appフォルダ構造 + +> **知っておくと便利**: +> +> - ルートレイアウトの作成を忘れた場合、`next dev`で開発サーバーを実行するとNext.jsが自動的にこのファイルを作成します +> - オプションで、プロジェクトのルートに[`src`フォルダ](/docs/app/api-reference/file-conventions/src-folder)を使用して、アプリケーションコードを設定ファイルから分離できます + +
+ + + +### `pages`ディレクトリの作成 + +Next.jsはファイルシステムベースのルーティングを使用しており、アプリケーション内のルートはファイルの構造によって決定されます。 + +プロジェクトのルートに`pages`ディレクトリを作成します。次に、`pages`フォルダ内に`index.tsx`ファイルを追加します。これがホームページ(`/`)になります: + +```tsx filename="pages/index.tsx" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +```jsx filename="pages/index.js" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +次に、`pages/`内に`_app.tsx`ファイルを追加してグローバルレイアウトを定義します。[カスタムAppファイル](/docs/pages/building-your-application/routing/custom-app)について詳しく学びましょう。 + +```tsx filename="pages/_app.tsx" switcher +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return +} +``` + +```jsx filename="pages/_app.js" switcher +export default function App({ Component, pageProps }) { + return +} +``` + +最後に、`pages/`内に`_document.tsx`ファイルを追加して、サーバーからの初期レスポンスを制御します。[カスタムDocumentファイル](/docs/pages/building-your-application/routing/custom-document)について詳しく学びましょう。 + +```tsx filename="pages/_document.tsx" switcher +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + +```jsx filename="pages/_document.js" switcher +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + + + +### `public`フォルダの作成(オプション) + +プロジェクトのルートに[`public`フォルダ](/docs/app/api-reference/file-conventions/public-folder)を作成して、画像、フォントなどの静的アセットを保存します。`public`内のファイルは、ベースURL(`/`)から始まるパスでコードから参照できます。 + +これらのアセットはルートパス(`/`)を使用して参照できます。例えば、`public/profile.png`は`/profile.png`として参照できます: + +```tsx filename="app/page.tsx" highlight={4} switcher +import Image from 'next/image' + +export default function Page() { + return Profile +} +``` + +```jsx filename="app/page.js" highlight={4} switcher +import Image from 'next/image' + +export default function Page() { + return Profile +} +``` + +## 開発サーバーの実行 + +1. `npm run dev`を実行して開発サーバーを起動します +2. `http://localhost:3000`にアクセスしてアプリケーションを表示します +3. `app/page.tsx``pages/index.tsx`ファイルを編集して保存し、ブラウザで更新された結果を確認します + +## TypeScriptのセットアップ + +> TypeScriptの最小バージョン: `v4.5.2` + +Next.jsにはTypeScriptサポートが組み込まれています。プロジェクトにTypeScriptを追加するには、ファイルを`.ts` / `.tsx`にリネームして`next dev`を実行します。Next.jsは必要な依存関係を自動的にインストールし、推奨設定オプションを含む`tsconfig.json`ファイルを追加します。 + + + +### IDEプラグイン + +Next.jsにはカスタムTypeScriptプラグインとタイプチェッカーが含まれており、VSCodeや他のコードエディターで高度なタイプチェックとオートコンプリートに使用できます。 + +VS Codeでプラグインを有効にするには: + +1. コマンドパレットを開きます(`Ctrl/⌘` + `Shift` + `P`) +2. "TypeScript: Select TypeScript Version"を検索 +3. "Use Workspace Version"を選択 + +TypeScriptコマンドパレット + + + +詳細については[TypeScriptリファレンス](/docs/app/api-reference/config/next-config-js/typescript)ページを参照してください。 + +## ESLintのセットアップ + +Next.jsにはESLintが組み込まれています。`create-next-app`で新しいプロジェクトを作成すると、必要なパッケージが自動的にインストールされ、適切な設定が行われます。 + +既存のプロジェクトに手動でESLintを追加するには、`next lint`をスクリプトとして`package.json`に追加します: + +```json filename="package.json" +{ + "scripts": { + "lint": "next lint" + } +} +``` + +次に、`npm run lint`を実行すると、インストールと設定プロセスがガイドされます。 + +```bash filename="ターミナル" +npm run lint +``` + +以下のようなプロンプトが表示されます: + +> ? ESLintをどのように設定しますか? +> +> ❯ Strict (推奨) +> Base +> Cancel + +- **Strict**: Next.jsの基本ESLint設定に加えて、より厳格なCore Web Vitalsルールセットを含みます。ESLintを初めて設定する開発者に推奨される設定です +- **Base**: Next.jsの基本ESLint設定を含みます +- **Cancel**: 設定をスキップします。独自のカスタムESLint設定を計画している場合はこのオプションを選択してください + +`Strict`または`Base`が選択された場合、Next.jsは自動的に`eslint`と`eslint-config-next`を依存関係としてインストールし、選択した設定を含む`.eslintrc.json`ファイルをプロジェクトのルートに作成します。 + +ESLintを実行してエラーを検出するには、`next lint`を実行します。ESLintが設定されると、ビルド時(`next build`)にも自動的に実行されます。エラーはビルドを失敗させますが、警告は失敗させません。 + +詳細については[ESLintプラグイン](/docs/app/api-reference/config/next-config-js/eslint)ページを参照してください。 + +## 絶対インポートとモジュールパスエイリアスの設定 + +Next.jsは`tsconfig.json`と`jsconfig.json`ファイルの`"paths"`および`"baseUrl"`オプションをサポートしています。 + +これらのオプションを使用すると、プロジェクトディレクトリを絶対パスにエイリアスできるため、モジュールのインポートがより簡単でクリーンになります。例えば: + +```jsx +// 変更前 +import { Button } from '../../../components/button' + +// 変更後 +import { Button } from '@/components/button' +``` + +絶対インポートを設定するには、`baseUrl`設定オプションを`tsconfig.json`または`jsconfig.json`ファイルに追加します。例えば: + +```json filename="tsconfig.json または jsconfig.json" +{ + "compilerOptions": { + "baseUrl": "src/" + } +} +``` + +`baseUrl`パスを設定するのに加えて、`"paths"`オプションを使用してモジュールパスを`"alias"`できます。 + +例えば、以下の設定は`@/components/*`を`components/*`にマッピングします: + +```json filename="tsconfig.json または jsconfig.json" +{ + "compilerOptions": { + "baseUrl": "src/", + "paths": { + "@/styles/*": ["styles/*"], + "@/components/*": ["components/*"] + } + } +} +``` + +各`"paths"`は`baseUrl`の場所からの相対パスです。 \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/02-project-structure.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/02-project-structure.mdx new file mode 100644 index 00000000..801a6858 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/02-project-structure.mdx @@ -0,0 +1,407 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:04:33.617Z +title: プロジェクト構造と構成 +nav_title: プロジェクト構造 +description: Next.jsにおけるフォルダとファイルの規約の概要と、プロジェクトの構成方法について。 +--- + +このページでは、Next.jsにおける**すべて**のフォルダとファイルの規約の概要と、プロジェクトを構成するための推奨事項を提供します。 + +## フォルダとファイルの規約 + +### トップレベルフォルダ + +トップレベルフォルダは、アプリケーションのコードと静的アセットを整理するために使用されます。 + +ルートセグメントからパスセグメントへ + +| | | +| ------------------------------------------------------------------ | ---------------------------------- | +| [`app`](/docs/app/building-your-application/routing) | App Router | +| [`pages`](/docs/pages/building-your-application/routing) | Pages Router | +| [`public`](/docs/app/api-reference/file-conventions/public-folder) | 配信される静的アセット | +| [`src`](/docs/app/api-reference/file-conventions/src-folder) | オプションのアプリケーションソースフォルダ | + +### トップレベルファイル + +トップレベルファイルは、アプリケーションの設定、依存関係の管理、ミドルウェアの実行、監視ツールの統合、環境変数の定義に使用されます。 + +| | | +| ---------------------------------------------------------------------------- | --------------------------------------- | +| **Next.js** | | +| [`next.config.js`](/docs/app/api-reference/config/next-config-js) | Next.jsの設定ファイル | +| [`package.json`](/docs/app/getting-started/installation#manual-installation) | プロジェクトの依存関係とスクリプト | +| [`instrumentation.ts`](/docs/app/guides/instrumentation) | OpenTelemetryと計装ファイル | +| [`middleware.ts`](/docs/app/building-your-application/routing/middleware) | Next.jsリクエストミドルウェア | +| [`.env`](/docs/app/guides/environment-variables) | 環境変数 | +| [`.env.local`](/docs/app/guides/environment-variables) | ローカル環境変数 | +| [`.env.production`](/docs/app/guides/environment-variables) | 本番環境変数 | +| [`.env.development`](/docs/app/guides/environment-variables) | 開発環境変数 | +| [`.eslintrc.json`](/docs/app/api-reference/config/eslint) | ESLintの設定ファイル | +| `.gitignore` | Gitで無視するファイルとフォルダ | +| `next-env.d.ts` | Next.jsのTypeScript宣言ファイル | +| `tsconfig.json` | TypeScriptの設定ファイル | +| `jsconfig.json` | JavaScriptの設定ファイル | + + + +### ルーティングファイル + +| | | | +| ----------------------------------------------------------------------------- | ------------------- | ---------------------------- | +| [`layout`](/docs/app/api-reference/file-conventions/layout) | `.js` `.jsx` `.tsx` | レイアウト | +| [`page`](/docs/app/api-reference/file-conventions/page) | `.js` `.jsx` `.tsx` | ページ | +| [`loading`](/docs/app/api-reference/file-conventions/loading) | `.js` `.jsx` `.tsx` | ローディングUI | +| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | `.js` `.jsx` `.tsx` | 404 UI | +| [`error`](/docs/app/api-reference/file-conventions/error) | `.js` `.jsx` `.tsx` | エラーUI | +| [`global-error`](/docs/app/api-reference/file-conventions/error#global-error) | `.js` `.jsx` `.tsx` | グローバルエラーUI | +| [`route`](/docs/app/api-reference/file-conventions/route) | `.js` `.ts` | APIエンドポイント | +| [`template`](/docs/app/api-reference/file-conventions/template) | `.js` `.jsx` `.tsx` | 再レンダリングされるレイアウト | +| [`default`](/docs/app/api-reference/file-conventions/default) | `.js` `.jsx` `.tsx` | パラレルルートのフォールバックページ | + +### ネストされたルート + +| | | +| --------------- | -------------------- | +| `folder` | ルートセグメント | +| `folder/folder` | ネストされたルートセグメント | + +### ダイナミックルート + +| | | +| ------------------------------------------------------------------------------------------------------ | -------------------------------- | +| [`[folder]`](/docs/app/api-reference/file-conventions/dynamic-routes#convention) | ダイナミックルートセグメント | +| [`[...folder]`](/docs/app/api-reference/file-conventions/dynamic-routes#catch-all-segments) | キャッチオールルートセグメント | +| [`[[...folder]]`](/docs/app/api-reference/file-conventions/dynamic-routes#optional-catch-all-segments) | オプションキャッチオールルートセグメント | + +### ルートグループとプライベートフォルダ + +| | | +| ------------------------------------------------------------------------------ | ------------------------------------------------ | +| [`(folder)`](/docs/app/api-reference/file-conventions/route-groups#convention) | ルーティングに影響を与えずにルートをグループ化 | +| [`_folder`](#private-folders) | フォルダとすべての子セグメントをルーティングから除外 | + +### パラレルルートとインターセプトリクエスト + +| | | +| ------------------------------------------------------------------------------------------- | -------------------------- | +| [`@folder`](/docs/app/api-reference/file-conventions/parallel-routes#slots) | 名前付きスロット | +| [`(.)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | 同じレベルでインターセプト | +| [`(..)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | 1レベル上でインターセプト | +| [`(..)(..)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | 2レベル上でインターセプト | +| [`(...)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | ルートからインターセプト | + +### メタデータファイル規約 + +#### アプリアイコン + +| | | | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------ | +| [`favicon`](/docs/app/api-reference/file-conventions/metadata/app-icons#favicon) | `.ico` | ファビコンファイル | +| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#icon) | `.ico` `.jpg` `.jpeg` `.png` `.svg` | アプリアイコンファイル | +| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | 生成されたアプリアイコン | +| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#apple-icon) | `.jpg` `.jpeg`, `.png` | Appleアプリアイコンファイル | +| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | 生成されたAppleアプリアイコン | + +#### Open GraphとTwitter画像 + +| | | | +| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -------------------------- | +| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#opengraph-image) | `.jpg` `.jpeg` `.png` `.gif` | Open Graph画像ファイル | +| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | 生成されたOpen Graph画像 | +| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#twitter-image) | `.jpg` `.jpeg` `.png` `.gif` | Twitter画像ファイル | +| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | 生成されたTwitter画像 | + +#### SEO + +| | | | +| ------------------------------------------------------------------------------------------------------------ | ----------- | --------------------- | +| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#sitemap-files-xml) | `.xml` | サイトマップファイル | +| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts) | `.js` `.ts` | 生成されたサイトマップ | +| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#static-robotstxt) | `.txt` | Robotsファイル | +| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file) | `.js` `.ts` | 生成されたRobotsファイル | + + + + + +### ファイル規約 + +| | | | +| ----------------------------------------------------------------------------------------------------------- | ------------------- | ----------------- | +| [`_app`](/docs/pages/building-your-application/routing/custom-app) | `.js` `.jsx` `.tsx` | カスタムApp | +| [`_document`](/docs/pages/building-your-application/routing/custom-document) | `.js` `.jsx` `.tsx` | カスタムDocument | +| [`_error`](/docs/pages/building-your-application/routing/custom-error#more-advanced-error-page-customizing) | `.js` `.jsx` `.tsx` | カスタムエラーページ | +| [`404`](/docs/pages/building-your-application/routing/custom-error#404-page) | `.js` `.jsx` `.tsx` | 404エラーページ | +| [`500`](/docs/pages/building-your-application/routing/custom-error#500-page) | `.js` `.jsx` `.tsx` | 500エラーページ | + +### ルート + +| | | | +| ---------------------------------------------------------------------------------------------- | ------------------- | ----------- | +| **フォルダ規約** | | | +| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | ホームページ | +| [`folder/index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | ネストされたページ | +| **ファイル規約** | | | +| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | ホームページ | +| [`file`](/docs/pages/building-your-application/routing/pages-and-layouts) | `.js` `.jsx` `.tsx` | ネストされたページ | + +### ダイナミックルート + +| | | | +| ----------------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------- | +| **フォルダ規約** | | | +| [`[folder]/index`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | ダイナミックルートセグメント | +| [`[...folder]/index`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | キャッチオールルートセグメント | +| [`[[...folder]]/index`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | オプションキャッチオールルートセグメント | +| **ファイル規約** | | | +| [`[file]`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | ダイナミックルートセグメント | +| [`[...file]`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | キャッチオールルートセグメント | +| [`[[...file]]`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | オプションキャッチオールルートセグメント | + + + + + +## プロジェクトの構成 + +Next.jsは、プロジェクトファイルをどのように整理して配置するかについて**意見を強制しません**。ただし、プロジェクトを整理するためのいくつかの機能を提供しています。 + +### コンポーネント階層 + +特別なファイルで定義されたコンポーネントは、特定の階層でレンダリングされます: + +- `layout.js` +- `template.js` +- `error.js` (Reactエラー境界) +- `loading.js` (Reactサスペンス境界) +- `not-found.js` (Reactエラー境界) +- `page.js` またはネストされた `layout.js` + +ファイル規約のコンポーネント階層 + +コンポーネントはネストされたルートで再帰的にレンダリングされるため、ルートセグメントのコンポーネントはその親セグメントのコンポーネントの**内側**にネストされます。 + +ネストされたファイル規約のコンポーネント階層 + + + +### コロケーション (Colocation) + +`app`ディレクトリでは、ネストされたフォルダがルート構造を定義します。各フォルダはURLパスの対応するセグメントにマッピングされるルートセグメントを表します。 + +ただし、ルート構造がフォルダで定義されていても、ルートセグメントに`page.js`または`route.js`ファイルが追加されるまで、そのルートは**公開アクセス可能になりません**。 + +ルートセグメントにpage.jsまたはroute.jsファイルが追加されるまでルートが公開されないことを示す図 + +また、ルートが公開アクセス可能になった場合でも、`page.js`または`route.js`が返す**コンテンツのみ**がクライアントに送信されます。 + +page.jsとroute.jsファイルがルートを公開アクセス可能にする仕組みを示す図 + +これは、`app`ディレクトリ内のルートセグメントに**プロジェクトファイルを安全にコロケーション(同居)**させても、誤ってルーティング可能にならないことを意味します。 + +セグメントにpage.jsやroute.jsファイルがあっても、コロケーションされたプロジェクトファイルはルーティングされないことを示す図 + +> **知っておくと便利**: `app`内にプロジェクトファイルをコロケーションすることは**可能**ですが、**必須ではありません**。必要に応じて[`app`ディレクトリの外に保管](#store-project-files-outside-of-app)することもできます。 + +### プライベートフォルダ (Private folders) + +フォルダ名の前にアンダースコアを付けることでプライベートフォルダを作成できます: `_folderName` + +これは、そのフォルダがルーティングシステムから考慮されないプライベートな実装詳細であることを示し、フォルダとそのすべてのサブフォルダをルーティングから**除外**します。 + +プライベートフォルダを使用したフォルダ構造の例 + +`app`ディレクトリ内のファイルは[デフォルトで安全にコロケーション可能](#colocation)なため、コロケーションのためにプライベートフォルダは必須ではありません。ただし、以下の用途に役立ちます: + +- UIロジックとルーティングロジックの分離 +- プロジェクト全体およびNext.jsエコシステムで内部ファイルを一貫して整理 +- コードエディタでのファイルの並べ替えとグループ化 +- 将来のNext.jsファイル規約との名前衝突の回避 + +> **知っておくと便利**: +> - フレームワークの規約ではありませんが、プライベートフォルダ外のファイルにも同じアンダースコアパターンで「プライベート」マークを付けることを検討できます。 +> - フォルダ名の前に`%5F`(アンダースコアのURLエンコード形式)を付けることで、アンダースコアで始まるURLセグメントを作成できます: `%5FfolderName` +> - プライベートフォルダを使用しない場合、Next.jsの[特別なファイル規約](/docs/app/getting-started/project-structure#routing-files)を知っておくと、予期しない名前衝突を防ぐのに役立ちます。 + +### ルートグループ (Route groups) + +フォルダを括弧で囲むことでルートグループを作成できます: `(folderName)` + +これは、そのフォルダが整理目的であり、ルートのURLパスに**含まれない**ことを示します。 + +ルートグループを使用したフォルダ構造の例 + +ルートグループは以下の用途に便利です: + +- サイトセクション、意図、またはチームごとにルートを整理(例: マーケティングページ、管理ページなど) +- 同じルートセグメントレベルでネストされたレイアウトを有効化: + - [同じセグメント内で複数のネストされたレイアウトを作成](#creating-multiple-root-layouts) + - [共通セグメント内のルートサブセットにレイアウトを追加](#opting-specific-segments-into-a-layout) + +### `src`フォルダ + +Next.jsはアプリケーションコード(`app`を含む)をオプションの[`src`フォルダ](/docs/app/api-reference/file-conventions/src-folder)内に保管することをサポートしています。これにより、アプリケーションコードとプロジェクトルートに主に存在する設定ファイルを分離できます。 + +`src`フォルダを使用したフォルダ構造の例 + +## 例 + +以下のセクションでは、一般的な戦略の概要を非常に高レベルで説明します。最も単純なポイントは、あなたとチームにとって機能する戦略を選択し、プロジェクト全体で一貫性を保つことです。 + +> **知っておくと便利**: 以下の例では、`components`や`lib`フォルダを一般的なプレースホルダーとして使用しています。これらの名前には特別なフレームワークの意味はなく、`ui`、`utils`、`hooks`、`styles`などの他のフォルダを使用する場合もあります。 + +### `app`の外にプロジェクトファイルを保管 + +この戦略では、すべてのアプリケーションコードをプロジェクトルートの共有フォルダに保管し、`app`ディレクトリは純粋にルーティング目的のみに使用します。 + +プロジェクトファイルをappの外に保管したフォルダ構造の例 + +### `app`内のトップレベルフォルダにプロジェクトファイルを保管 + +この戦略では、すべてのアプリケーションコードを`app`ディレクトリのルートにある共有フォルダに保管します。 + +プロジェクトファイルをapp内に保管したフォルダ構造の例 + +### 機能やルートごとにプロジェクトファイルを分割 + +この戦略では、グローバルに共有されるアプリケーションコードを`app`ディレクトリのルートに保管し、より特定のアプリケーションコードをそれらを使用するルートセグメントに**分割**します。 + +機能やルートごとにプロジェクトファイルを分割したフォルダ構造の例 + +### URLパスに影響を与えずにルートを整理 + +URLに影響を与えずにルートを整理するには、関連するルートをまとめて保持するグループを作成します。括弧内のフォルダはURLから除外されます(例: `(marketing)`や`(shop)`)。 + +ルートグループを使用したルート整理 + +`(marketing)`と`(shop)`内のルートは同じURL階層を共有していますが、各グループのフォルダ内に`layout.js`ファイルを追加することで、グループごとに異なるレイアウトを作成できます。 + +複数のレイアウトを持つルートグループ + +### 特定のセグメントをレイアウトにオプトイン + +特定のルートをレイアウトにオプトインするには、新しいルートグループ(例: `(shop)`)を作成し、同じレイアウトを共有するルート(例: `account`と`cart`)をそのグループに移動します。グループ外のルート(例: `checkout`)はレイアウトを共有しません。 + +オプトインレイアウトを持つルートグループ + +### 特定のルートにローディングスケルトンを適用 + +[ローディングスケルトン](/docs/app/building-your-application/routing/loading-ui-and-streaming)を`loading.js`ファイルで特定のルートに適用するには、新しいルートグループ(例: `/(overview)`)を作成し、そのルートグループ内に`loading.tsx`を配置します。 + +ルートグループ内のloading.tsxとpage.tsxを示すフォルダ構造 + +これで、`loading.tsx`ファイルはダッシュボード→概要ページにのみ適用され、URLパス構造に影響を与えずにすべてのダッシュボードページに適用されることはありません。 + +### 複数のルートレイアウトを作成 + +複数の[ルートレイアウト](/docs/app/api-reference/file-conventions/layout#root-layout)を作成するには、トップレベルの`layout.js`ファイルを削除し、各ルートグループ内に`layout.js`ファイルを追加します。これは、完全に異なるUIやエクスペリエンスを持つセクションにアプリケーションを分割する場合に便利です。``タグと``タグは各ルートレイアウトに追加する必要があります。 + +複数のルートレイアウトを持つルートグループ + +上記の例では、`(marketing)`と`(shop)`の両方が独自のルートレイアウトを持っています。 + + diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/03-layouts-and-pages.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/03-layouts-and-pages.mdx new file mode 100644 index 00000000..83e24748 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/03-layouts-and-pages.mdx @@ -0,0 +1,294 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:02:08.927Z +title: レイアウトとページの作成方法 +nav_title: レイアウトとページ +description: 最初のページとレイアウトを作成し、それらをリンクする方法について説明します。 +related: + title: APIリファレンス + description: このページで言及されている機能について詳しく知るには、APIリファレンスを参照してください。 + links: + - app/api-reference/file-conventions/layout + - app/api-reference/file-conventions/page + - app/api-reference/components/link + - app/api-reference/file-conventions/dynamic-routes +--- + +Next.jsでは**ファイルシステムベースのルーティング**を使用しており、フォルダとファイルを使ってルートを定義できます。このページでは、レイアウトとページの作成方法、およびそれら間のリンク方法について説明します。 + +## ページの作成 + +**ページ**とは、特定のルートでレンダリングされるUIです。ページを作成するには、`app`ディレクトリ内に[`page`ファイル](/docs/app/api-reference/file-conventions/page)を追加し、Reactコンポーネントをデフォルトエクスポートします。例えば、インデックスページ(`/`)を作成する場合: + +page.js特殊ファイル + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

Hello Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

Hello Next.js!

+} +``` + +## レイアウトの作成 + +レイアウトとは、複数のページ間で**共有**されるUIです。ナビゲーション時、レイアウトは状態を保持し、インタラクティブなまま再レンダリングされません。 + +[`layout`ファイル](/docs/app/api-reference/file-conventions/layout)からReactコンポーネントをデフォルトエクスポートすることでレイアウトを定義できます。このコンポーネントは、ページまたは別の[レイアウト](#nesting-layouts)となる`children`プロップを受け取る必要があります。 + +例えば、インデックスページを子として受け取るレイアウトを作成するには、`app`ディレクトリ内に`layout`ファイルを追加します: + +layout.js特殊ファイル + +```tsx filename="app/layout.tsx" switcher +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {/* レイアウトUI */} + {/* ページまたはネストされたレイアウトをレンダリングしたい場所にchildrenを配置 */} +
{children}
+ + + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function DashboardLayout({ children }) { + return ( + + + {/* レイアウトUI */} + {/* ページまたはネストされたレイアウトをレンダリングしたい場所にchildrenを配置 */} +
{children}
+ + + ) +} +``` + +上記のレイアウトは[ルートレイアウト](/docs/app/api-reference/file-conventions/layout#root-layout)と呼ばれ、`app`ディレクトリのルートで定義されています。ルートレイアウトは**必須**で、`html`タグと`body`タグを含める必要があります。 + +## ネストされたルートの作成 + +ネストされたルートとは、複数のURLセグメントで構成されるルートです。例えば、`/blog/[slug]`ルートは3つのセグメントで構成されます: + +- `/`(ルートセグメント) +- `blog`(セグメント) +- `[slug]`(リーフセグメント) + +Next.jsでは: + +- **フォルダ**はURLセグメントにマッピングされるルートセグメントを定義するために使用されます +- **ファイル**(`page`や`layout`など)はセグメントに対して表示されるUIを作成するために使用されます + +ネストされたルートを作成するには、フォルダを互いに入れ子にします。例えば、`/blog`のルートを追加するには、`app`ディレクトリ内に`blog`というフォルダを作成します。そして、`/blog`を公開可能にするために、`page.tsx`ファイルを追加します: + +blogフォルダとpage.jsファイルを示すファイル階層 + +```tsx filename="app/blog/page.tsx" switcher +// ダミーインポート +import { getPosts } from '@/lib/posts' +import { Post } from '@/ui/post' + +export default async function Page() { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( + + ))} +
+ ) +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +// ダミーインポート +import { getPosts } from '@/lib/posts' +import { Post } from '@/ui/post' + +export default async function Page() { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( + + ))} +
+ ) +} +``` + +フォルダをさらにネストすることで、ネストされたルートを作成できます。例えば、特定のブログ投稿のルートを作成するには、`blog`内に新しい`[slug]`フォルダを作成し、`page`ファイルを追加します: + +blogフォルダ内にネストされたslugフォルダとpage.jsファイルを示すファイル階層 + +```tsx filename="app/blog/[slug]/page.tsx" switcher +function generateStaticParams() {} + +export default function Page() { + return

Hello, Blog Post Page!

+} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +function generateStaticParams() {} + +export default function Page() { + return

Hello, Blog Post Page!

+} +``` + +フォルダ名を角括弧で囲む(例:`[slug]`)ことで、[動的ルートセグメント](/docs/app/api-reference/file-conventions/dynamic-routes)を作成し、データから複数のページを生成できます。例:ブログ投稿、商品ページなど。 + +## レイアウトのネスト + +デフォルトでは、フォルダ階層内のレイアウトもネストされており、`children`プロップを介して子レイアウトをラップします。特定のルートセグメント(フォルダ)内に`layout`を追加することでレイアウトをネストできます。 + +例えば、`/blog`ルートのレイアウトを作成するには、`blog`フォルダ内に新しい`layout`ファイルを追加します。 + +ルートレイアウトがblogレイアウトをラップしているファイル階層 + +```tsx filename="app/blog/layout.tsx" switcher +export default function BlogLayout({ + children, +}: { + children: React.ReactNode +}) { + return
{children}
+} +``` + +```jsx filename="app/blog/layout.js" switcher +export default function BlogLayout({ children }) { + return
{children}
+} +``` + +上記の2つのレイアウトを組み合わせると、ルートレイアウト(`app/layout.js`)がブログレイアウト(`app/blog/layout.js`)をラップし、それがブログ(`app/blog/page.js`)とブログ投稿ページ(`app/blog/[slug]/page.js`)をラップします。 + +## 動的セグメントの作成 + +[動的セグメント](/docs/app/api-reference/file-conventions/dynamic-routes)を使用すると、データから生成されるルートを作成できます。例えば、個々のブログ投稿ごとに手動でルートを作成する代わりに、ブログ投稿データに基づいてルートを生成する動的セグメントを作成できます。 + +動的セグメントを作成するには、セグメント(フォルダ)名を角括弧で囲みます:`[segmentName]`。例えば、`app/blog/[slug]/page.tsx`ルートでは、`[slug]`が動的セグメントです。 + +```tsx filename="app/blog/[slug]/page.tsx" switcher +export default async function BlogPostPage({ + params, +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params + const post = await getPost(slug) + + return ( +
+

{post.title}

+

{post.content}

+
+ ) +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +export default async function BlogPostPage({ params }) { + const { slug } = await params + const post = await getPost(slug) + + return ( +
+

{post.title}

+

{post.content}

+
+ ) +} +``` + +[動的セグメント](/docs/app/api-reference/file-conventions/dynamic-routes)についてさらに学びましょう。 + +## ページ間のリンク + +[``コンポーネント](/docs/app/api-reference/components/link)を使用してルート間をナビゲートできます。``はNext.jsに組み込まれたコンポーネントで、HTMLの``タグを拡張し、[プリフェッチ](/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching)と[クライアントサイドナビゲーション](/docs/app/building-your-application/routing/linking-and-navigating#5-soft-navigation)を提供します。 + +例えば、ブログ投稿のリストを生成するには、`next/link`から``をインポートし、`href`プロップをコンポーネントに渡します: + +```tsx filename="app/ui/post.tsx" highlight={1,10} switcher +import Link from 'next/link' + +export default async function Post({ post }) { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( +
  • + {post.title} +
  • + ))} +
+ ) +} +``` + +```jsx filename="app/ui/post.js" highlight={1,10} switcher +import Link from 'next/link' + +export default async function Post({ post }) { + const posts = await getPosts() + + return ( +
    + {posts.map((post) => ( +
  • + {post.title} +
  • + ))} +
+ ) +} +``` + +``はNext.jsアプリケーションでルート間をナビゲートする主要かつ推奨される方法です。ただし、より高度なナビゲーションには[`useRouter`フック](/docs/app/api-reference/functions/use-router)も使用できます。 \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/04-images.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/04-images.mdx new file mode 100644 index 00000000..c4223517 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/04-images.mdx @@ -0,0 +1,199 @@ +--- +source-updated-at: 2025-06-02T15:30:01.000Z +translation-updated-at: 2025-06-02T20:01:10.215Z +title: 画像最適化の方法 +nav_title: 画像 +description: Next.jsにおける画像最適化について学ぶ +related: + title: APIリファレンス + description: Next.js Imageの全機能についてはAPIリファレンスを参照してください。 + links: + - app/api-reference/components/image +--- + +Next.jsの[``](/docs/app/api-reference/components/image)コンポーネントはHTMLの``要素を拡張し、以下の機能を提供します: + +- **サイズ最適化:** WebPなどの最新画像フォーマットを使用し、各デバイスに適切なサイズの画像を自動的に配信 +- **視覚的安定性:** 画像読み込み時の[レイアウトシフト](https://web.dev/articles/cls)を自動的に防止 +- **高速なページ読み込み:** ブラウザのネイティブな遅延読み込みを使用して、ビューポートに入った画像のみを読み込み、オプションでブラーアッププレースホルダーを表示 +- **アセットの柔軟性:** リモートサーバーに保存されている画像でもオンデマンドでサイズ変更可能 + +``を使用するには、`next/image`からインポートし、コンポーネント内でレンダリングします。 + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return +} +``` + +`src`プロパティには[ローカル](#ローカル画像)または[リモート](#リモート画像)の画像を指定できます。 + +> **🎥 動画:** `next/image`の使用方法についてさらに学ぶ → [YouTube (9分)](https://youtu.be/IU_qq_c_lKA). + +## ローカル画像 + +画像やフォントなどの静的ファイルは、ルートディレクトリの[`public`](/docs/app/api-reference/file-conventions/public-folder)フォルダ以下に保存できます。`public`内のファイルは、ベースURL(`/`)から始まるパスで参照できます。 + +appフォルダとpublicフォルダのディレクトリ構造 + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +### 静的インポート画像 + +ローカルの画像ファイルをインポートして使用することもできます。Next.jsはインポートされたファイルに基づいて、自動的に画像の固有の[`width`](/docs/app/api-reference/components/image#width-and-height)と[`height`](/docs/app/api-reference/components/image#width-and-height)を決定します。これらの値は画像のアスペクト比を決定し、画像読み込み時の[累積レイアウトシフト(CLS)](https://web.dev/articles/cls)を防ぐために使用されます。 + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' +import ProfileImage from './profile.png' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' +import ProfileImage from './profile.png' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +この場合、Next.jsは`app/profile.png`ファイルが利用可能であることを期待します。 + +## リモート画像 + +リモート画像を使用するには、`src`プロパティにURL文字列を指定します。 + +```tsx filename="app/page.tsx" switcher +import Image from 'next/image' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +```jsx filename="app/page.js" switcher +import Image from 'next/image' + +export default function Page() { + return ( + 著者の写真 + ) +} +``` + +Next.jsはビルド時にリモートファイルにアクセスできないため、[`width`](/docs/app/api-reference/components/image#width-and-height)、[`height`](/docs/app/api-reference/components/image#width-and-height)、およびオプションの[`blurDataURL`](/docs/app/api-reference/components/image#blurdataurl)プロパティを手動で指定する必要があります。`width`と`height`は、画像の正しいアスペクト比を推測し、画像読み込み時のレイアウトシフトを防ぐために使用されます。 + +リモートサーバーからの画像を安全に許可するには、[`next.config.js`](/docs/app/api-reference/config/next-config-js)でサポートするURLパターンのリストを定義する必要があります。悪意のある使用を防ぐため、可能な限り具体的に指定してください。例えば、以下の設定では特定のAWS S3バケットからのみ画像を許可します: + +```ts filename="next.config.ts" switcher +import type { NextConfig } from 'next' + +const config: NextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.amazonaws.com', + port: '', + pathname: '/my-bucket/**', + search: '', + }, + ], + }, +} + +export default config +``` + +```js filename="next.config.js" switcher +module.exports = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.amazonaws.com', + port: '', + pathname: '/my-bucket/**', + search: '', + }, + ], + }, +} +``` \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/05-fonts.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/05-fonts.mdx new file mode 100644 index 00000000..d4b40542 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/05-fonts.mdx @@ -0,0 +1,203 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:01:01.061Z +title: フォントの使用方法 +nav_title: フォント +description: Next.jsでフォントを使用する方法を学ぶ +related: + title: APIリファレンス + description: Next.js Fontの全機能セットについてはAPIリファレンスを参照してください + links: + - app/api-reference/components/font +--- + +[`next/font`](/docs/app/api-reference/components/font)モジュールは、フォントを自動的に最適化し、プライバシーとパフォーマンスを向上させるために外部ネットワークリクエストを削除します。 + +**ビルトインのセルフホスティング**機能により、あらゆるフォントファイルを利用できます。つまり、レイアウトシフトなしでウェブフォントを最適に読み込むことができます。 + +`next/font`を使用するには、[`next/font/local`](#ローカルフォント)または[`next/font/google`](#googleフォント)からインポートし、適切なオプションを指定して関数として呼び出し、フォントを適用したい要素の`className`を設定します。例: + +```tsx filename="app/layout.tsx" highlight={1,3-5,9} switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" highlight={1,3-5,9} switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function Layout({ children }) { + return ( + + {children} + + ) +} +``` + +フォントは使用されるコンポーネントにスコープされます。アプリケーション全体にフォントを適用するには、[ルートレイアウト](/docs/app/api-reference/file-conventions/layout#root-layout)に追加します。 + +## Googleフォント + +Google Fontsを自動的にセルフホストできます。フォントは静的アセットとして含まれ、デプロイと同じドメインから提供されるため、ユーザーがサイトを訪れた際にブラウザからGoogleへのリクエストが送信されません。 + +Google Fontを使用するには、`next/font/google`から選択したフォントをインポートします: + +```tsx filename="app/layout.tsx" switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Geist } from 'next/font/google' + +const geist = Geist({ + subsets: ['latin'], +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +最高のパフォーマンスと柔軟性のために[可変フォント](https://fonts.google.com/variablefonts)を使用することを推奨します。可変フォントが使用できない場合は、ウェイトを指定する必要があります: + +```tsx filename="app/layout.tsx" highlight={4} switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" highlight={4} switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +## ローカルフォント + +ローカルフォントを使用するには、`next/font/local`からフォントをインポートし、ローカルフォントファイルの[`src`](/docs/app/api-reference/components/font#src)を指定します。フォントは[`public`](/docs/app/api-reference/file-conventions/public-folder)フォルダに保存できます。例: + +```tsx filename="app/layout.tsx" switcher +import localFont from 'next/font/local' + +const myFont = localFont({ + src: './my-font.woff2', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import localFont from 'next/font/local' + +const myFont = localFont({ + src: './my-font.woff2', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +単一のフォントファミリーに複数のファイルを使用したい場合、`src`は配列にできます: + +```js +const roboto = localFont({ + src: [ + { + path: './Roboto-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './Roboto-Italic.woff2', + weight: '400', + style: 'italic', + }, + { + path: './Roboto-Bold.woff2', + weight: '700', + style: 'normal', + }, + { + path: './Roboto-BoldItalic.woff2', + weight: '700', + style: 'italic', + }, + ], +}) +``` \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/06-css.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/06-css.mdx new file mode 100644 index 00000000..30a32cbe --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/06-css.mdx @@ -0,0 +1,295 @@ +--- +source-updated-at: 2025-05-25T15:16:02.000Z +translation-updated-at: 2025-06-02T20:01:46.267Z +title: アプリケーションでCSSを使用する方法 +nav_title: CSS +description: CSSモジュール、グローバルCSS、Tailwind CSSなど、アプリケーションにCSSを追加するさまざまな方法について学びましょう。 +related: + title: 次のステップ + description: アプリケーションでCSSを使用する他の方法についてさらに学びましょう。 + links: + - app/guides/tailwind-css + - app/guides/sass + - app/guides/css-in-js +--- + +Next.jsでは、以下のようなさまざまな方法でCSSを使用できます: + +- [CSSモジュール](#css-modules) +- [グローバルCSS](#global-css) +- [外部スタイルシート](#external-stylesheets) +- [Tailwind CSS](/docs/app/guides/tailwind-css) +- [Sass](/docs/app/guides/sass) +- [CSS-in-JS](/docs/app/guides/css-in-js) + +## CSSモジュール + +CSSモジュールは、一意のクラス名を生成することでCSSをローカルスコープ化します。これにより、異なるファイルで同じクラス名を使用しても衝突を心配する必要がありません。 + + + +CSSモジュールを使い始めるには、`.module.css`拡張子のファイルを作成し、`app`ディレクトリ内の任意のコンポーネントにインポートします: + +```css filename="app/blog/blog.module.css" +.blog { + padding: 24px; +} +``` + +```tsx filename="app/blog/page.tsx" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +```jsx filename="app/blog/page.js" switcher +import styles from './blog.module.css' + +export default function Layout() { + return
+} +``` + +
+ + + +CSSモジュールを使い始めるには、`.module.css`拡張子のファイルを作成し、`pages`ディレクトリ内の任意のコンポーネントにインポートします: + +```css filename="/styles/blog.module.css" +.blog { + padding: 24px; +} +``` + +```tsx filename="pages/blog/index.tsx" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +```jsx filename="pages/blog/index.js" switcher +import styles from './blog.module.css' + +export default function Page() { + return
+} +``` + +
+ +## グローバルCSS + +アプリケーション全体にスタイルを適用するためにグローバルCSSを使用できます。 + + + +`app/global.css`ファイルを作成し、ルートレイアウトにインポートすると、アプリケーションの**すべてのルート**にスタイルが適用されます: + +```css filename="app/global.css" +body { + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} +``` + +```tsx filename="app/layout.tsx" switcher +// これらのスタイルはアプリケーションのすべてのルートに適用されます +import './global.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +// これらのスタイルはアプリケーションのすべてのルートに適用されます +import './global.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **豆知識:** グローバルスタイルは`app`ディレクトリ内の任意のレイアウト、ページ、またはコンポーネントにインポートできます。ただし、Next.jsはスタイルシートの統合にReactの組み込みサスペンス機能を使用しているため、現在はルート間を移動してもスタイルシートが削除されず、競合が発生する可能性があります。本当にグローバルなCSSにはグローバルスタイルを、スコープされたCSSには[CSSモジュール](#css-modules)を使用することをお勧めします。 + + + + + +`pages/_app.js`ファイルにスタイルシートをインポートすると、アプリケーションの**すべてのルート**にスタイルが適用されます: + +```tsx filename="pages/_app.js" +import '@/styles/global.css' + +export default function MyApp({ Component, pageProps }) { + return +} +``` + +スタイルシートのグローバルな性質と競合を避けるため、[`pages/_app.js`](/docs/pages/building-your-application/routing/custom-app)内でインポートする必要があります。 + + + +## 外部スタイルシート + + + +外部パッケージで公開されているスタイルシートは、`app`ディレクトリ内のどこでもインポートできます(コンポーネントと同じ場所に配置することも可能です): + +```tsx filename="app/layout.tsx" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **豆知識:** React 19では、``も使用できます。詳細は[React `link`ドキュメント](https://react.dev/reference/react-dom/components/link)を参照してください。 + + + + + +Next.jsでは、JavaScriptファイルからCSSファイルをインポートできます。 +これは、Next.jsがJavaScriptを超えて[`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import)の概念を拡張しているため可能です。 + +### `node_modules`からのスタイルインポート + +Next.js **9.5.4**以降、`node_modules`からのCSSファイルのインポートがアプリケーションのどこでも許可されています。 + +`bootstrap`や`nprogress`のようなグローバルスタイルシートは、`pages/_app.js`内でインポートする必要があります。例: + +```jsx filename="pages/_app.js" +import 'bootstrap/dist/css/bootstrap.css' + +export default function MyApp({ Component, pageProps }) { + return +} +``` + +サードパーティコンポーネントに必要なCSSは、コンポーネント内でインポートできます。例: + +```jsx filename="components/example-dialog.js" +import { useState } from 'react' +import { Dialog } from '@reach/dialog' +import VisuallyHidden from '@reach/visually-hidden' +import '@reach/dialog/styles.css' + +function ExampleDialog(props) { + const [showDialog, setShowDialog] = useState(false) + const open = () => setShowDialog(true) + const close = () => setShowDialog(false) + + return ( +
+ + + +

こんにちは。私はダイアログです

+
+
+ ) +} +``` + +
+ +## 順序と結合 + +Next.jsは、本番ビルド時にスタイルシートを自動的にチャンク(結合)することでCSSを最適化します。**CSSの順序**は、**コード内でスタイルをインポートする順序**に依存します。 + +例えば、``が`page.module.css`より前にインポートされているため、`base-button.module.css`は`page.module.css`より前に順序付けられます: + +```tsx filename="page.ts" switcher +import { BaseButton } from './base-button' +import styles from './page.module.css' + +export default function Page() { + return +} +``` + +```jsx filename="page.js" switcher +import { BaseButton } from './base-button' +import styles from './page.module.css' + +export default function Page() { + return +} +``` + +```tsx filename="base-button.tsx" switcher +import styles from './base-button.module.css' + +export function BaseButton() { + return + + ) +} +``` + +```jsx filename="app/ui/counter.tsx" highlight={1} switcher +'use client' + +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + + return ( +
+

{count} likes

+ +
+ ) +} +``` + +`"use client"`は、サーバーとクライアントのモジュールグラフ(ツリー)間の**境界**を宣言するために使用されます。 + +ファイルに`"use client"`がマークされると、**そのすべてのインポートと子コンポーネントはクライアントバンドルの一部と見なされます**。つまり、クライアント向けのすべてのコンポーネントにディレクティブを追加する必要はありません。 + +### JSバンドルサイズの削減 + +クライアントJavaScriptバンドルのサイズを削減するには、UIの大部分をクライアントコンポーネントとしてマークする代わりに、特定のインタラクティブなコンポーネントに`'use client'`を追加します。 + +例えば、``コンポーネントにはロゴやナビゲーションリンクなどの静的な要素がほとんどですが、インタラクティブな検索バーが含まれています。``はインタラクティブでクライアントコンポーネントである必要がありますが、レイアウトの残りの部分はサーバーコンポーネントのままにできます。 + +```tsx filename="app/ui/search.tsx" highlight={1} switcher +'use client' + +export default function Search() { + // ... +} +``` + +```jsx filename="app/ui/search.js" highlight={1} switcher +'use client' + +export default function Search() { + // ... +} +``` + +```tsx filename="app/layout.tsx" switcher +// クライアントコンポーネント +import Search from './search' +// サーバーコンポーネント +import Logo from './logo' + +// レイアウトはデフォルトでサーバーコンポーネント +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + +
{children}
+ + ) +} +``` + +```jsx filename="app/layout.js" switcher +// クライアントコンポーネント +import Search from './search' +// サーバーコンポーネント +import Logo from './logo' + +// レイアウトはデフォルトでサーバーコンポーネント +export default function Layout({ children }) { + return ( + <> + +
{children}
+ + ) +} +``` + +### サーバーからクライアントコンポーネントへのデータ受け渡し + +propsを使用して、サーバーコンポーネントからクライアントコンポーネントにデータを渡すことができます。 + +```tsx filename="app/[id]/page.tsx" highlight={1,7} switcher +import LikeButton from '@/app/ui/like-button' +import { getPost } from '@/lib/data' + +export default async function Page({ params }: { params: { id: string } }) { + const post = await getPost(params.id) + + return +} +``` + +```jsx filename="app/[id]/page.js" highlight={1,7} switcher +import LikeButton from '@/app/ui/like-button' +import { getPost } from '@/lib/data' + +export default async function Page({ params }) { + const post = await getPost(params.id) + + return +} +``` + +```tsx filename="app/ui/like-button.tsx" highlight={1} switcher +'use client' + +export default function LikeButton({ likes }: { likes: number }) { + // ... +} +``` + +```jsx filename="app/ui/like-button.js" highlight={1} switcher +'use client' + +export default function LikeButton({ likes }) { + // ... +} +``` + +あるいは、サーバーコンポーネントからクライアントコンポーネントに[`use`フック](https://react.dev/reference/react/use)を使用してデータをストリーミングできます。[例](/docs/app/getting-started/fetching-data#streaming-data-with-the-use-hook)を参照してください。 + +> **豆知識**: クライアントコンポーネントに渡されるpropsは、Reactによって[シリアライズ可能](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values)である必要があります。 + +### サーバーとクライアントコンポーネントの交互配置 + +サーバーコンポーネントをクライアントコンポーネントのpropとして渡すことができます。これにより、クライアントコンポーネント内にサーバーレンダリングされたUIを視覚的にネストできます。 + +一般的なパターンは、`children`を使用して``内に_スロット_を作成することです。例えば、サーバー上でデータを取得する``コンポーネントを、クライアント状態を使用して表示/非表示を切り替える``コンポーネント内に配置します。 + +```tsx filename="app/ui/modal.tsx" switcher +'use client' + +export default function Modal({ children }: { children: React.ReactNode }) { + return
{children}
+} +``` + +```jsx filename="app/ui/modal.js" switcher +'use client' + +export default function Modal({ children }) { + return
{children}
+} +``` + +そして、親のサーバーコンポーネント(例: ``)で、``を``の子として渡します: + +```tsx filename="app/page.tsx" highlight={7} switcher +import Modal from './ui/modal' +import Cart from './ui/cart' + +export default function Page() { + return ( + + + + ) +} +``` + +```jsx filename="app/page.js" highlight={7} switcher +import Modal from './ui/modal' +import Cart from './ui/cart' + +export default function Page() { + return ( + + + + ) +} +``` + +このパターンでは、すべてのサーバーコンポーネントがpropsとして含まれるものも含め、事前にサーバー上でレンダリングされます。結果のRSCペイロードには、コンポーネントツリー内のどこにクライアントコンポーネントをレンダリングすべきかの参照が含まれます。 + +### コンテキストプロバイダ + +[Reactコンテキスト](https://react.dev/learn/passing-data-deeply-with-context)は、現在のテーマなどのグローバル状態を共有するためによく使用されます。ただし、Reactコンテキストはサーバーコンポーネントではサポートされていません。 + +コンテキストを使用するには、`children`を受け入れるクライアントコンポーネントを作成します: + +```tsx filename="app/theme-provider.tsx" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ + children, +}: { + children: React.ReactNode +}) { + return {children} +} +``` + +```jsx filename="app/theme-provider.js" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ children }) { + return {children} +} +``` + +次に、サーバーコンポーネント(例: `layout`)にインポートします: + +```tsx filename="app/layout.tsx" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +これで、サーバーコンポーネントが直接プロバイダをレンダリングできるようになり、アプリ全体のすべてのクライアントコンポーネントがこのコンテキストを利用できるようになります。 + +> **豆知識**: プロバイダはツリーのできるだけ深い場所にレンダリングする必要があります - `ThemeProvider`が``ドキュメント全体ではなく`{children}`のみをラップしていることに注目してください。これにより、Next.jsがサーバーコンポーネントの静的部分を最適化しやすくなります。 + +### サードパーティコンポーネント + +クライアント専用機能に依存するサードパーティコンポーネントを使用する場合、クライアントコンポーネントでラップすることで期待通りに動作させることができます。 + +例えば、``を`acme-carousel`パッケージからインポートできます。このコンポーネントは`useState`を使用していますが、まだ`"use client"`ディレクティブがありません。 + +``をクライアントコンポーネント内で使用すると、期待通りに動作します: + +```tsx filename="app/gallery.tsx" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ + {/* クライアントコンポーネント内で使用されているため動作 */} + {isOpen && } +
+ ) +} +``` + +```jsx filename="app/gallery.js" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + const [isOpen, setIsOpen] = useState(false) + + return ( +
+ + {/* クライアントコンポーネント内で使用されているため動作 */} + {isOpen && } +
+ ) +} +``` + +しかし、サーバーコンポーネント内で直接使用しようとすると、エラーが表示されます。これはNext.jsが``がクライアント専用機能を使用していることを認識しないためです。 + +これを修正するには、クライアント専用機能に依存するサードパーティコンポーネントを独自のクライアントコンポーネントでラップします: + +```tsx filename="app/carousel.tsx" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +```jsx filename="app/carousel.js" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +これで、サーバーコンポーネント内で直接``を使用できます: + +```tsx filename="app/page.tsx" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

View pictures

+ {/* Carouselがクライアントコンポーネントなので動作 */} + +
+ ) +} +``` + +```jsx filename="app/page.js" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

View pictures

+ {/* Carouselがクライアントコンポーネントなので動作 */} + +
+ ) +} +``` + +> **ライブラリ作者へのアドバイス** +> +> コンポーネントライブラリを構築している場合、クライアント専用機能に依存するエントリポイントに`"use client"`ディレクティブを追加してください。これにより、ユーザーがラッパーを作成することなくサーバーコンポーネントにコンポーネントをインポートできます。 +> +> 一部のバンドラーは`"use client"`ディレクティブを削除する可能性があることに注意してください。`"use client"`ディレクティブを含めるようにesbuildを設定する方法の例は、[React Wrap Balancer](https://github.com/shuding/react-wrap-balancer/blob/main/tsup.config.ts#L10-L13)と[Vercel Analytics](https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js#L26-L30)リポジトリで確認できます。 + +### 環境変数の流出防止 + +JavaScriptモジュールはサーバーコンポーネントとクライアントコンポーネントの間で共有可能です。つまり、誤ってサーバー専用のコードをクライアント側にインポートしてしまう可能性があります。例えば、以下の関数を考えてみましょう: + +```ts filename="lib/data.ts" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +```js filename="lib/data.js" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +この関数にはクライアントに公開してはいけない`API_KEY`が含まれています。 + +Next.jsでは、`NEXT_PUBLIC_`で始まる環境変数のみがクライアントバンドルに含まれます。プレフィックスがない変数は、Next.jsによって空文字列に置き換えられます。 + +その結果、`getData()`をクライアント側でインポートして実行しても、期待通りには動作しません。 + +クライアントコンポーネントでの誤使用を防ぐには、[`server-only`パッケージ](https://www.npmjs.com/package/server-only)を使用できます。 + +```bash filename="Terminal" +npm install server-only +``` + +そして、サーバー専用コードを含むファイルにこのパッケージをインポートします: + +```js filename="lib/data.js" +import 'server-only' + +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +これで、このモジュールをクライアントコンポーネントにインポートしようとすると、ビルド時にエラーが発生します。 + +> **補足**: 対応する[`client-only`パッケージ](https://www.npmjs.com/package/client-only)を使用すると、`window`オブジェクトにアクセスするコードなど、クライアント専用ロジックを含むモジュールをマークできます。 diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/08-fetching-data.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/08-fetching-data.mdx new file mode 100644 index 00000000..ed6d5b7c --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/08-fetching-data.mdx @@ -0,0 +1,658 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:03:52.469Z +title: データ取得とストリーミングの方法 +nav_title: データ取得 +description: アプリケーションでデータを取得し、コンテンツをストリーミングする方法について説明します。 +related: + title: APIリファレンス + description: このページで紹介されている機能について詳しく知るには、APIリファレンスをご覧ください。 + links: + - app/api-reference/functions/fetch + - app/api-reference/file-conventions/loading + - app/api-reference/config/next-config-js/logging + - app/api-reference/config/next-config-js/taint +--- + +このページでは、[サーバーコンポーネントとクライアントコンポーネント](/docs/app/getting-started/server-and-client-components)でデータを取得する方法と、データに依存するコンポーネントを[ストリーミング](#streaming)する方法について説明します。 + +## データの取得 + +### サーバーコンポーネント + +サーバーコンポーネントでは以下の方法でデータを取得できます: + +1. [`fetch` API](#with-the-fetch-api)を使用する +2. [ORMまたはデータベース](#with-an-orm-or-database)を使用する + +#### `fetch` APIを使用する + +`fetch` APIでデータを取得するには、コンポーネントを非同期関数に変更し、`fetch`呼び出しをawaitします。例: + +```tsx filename="app/blog/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://api.vercel.app/blog') + const posts = await data.json() + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +export default async function Page() { + const data = await fetch('https://api.vercel.app/blog') + const posts = await data.json() + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +> **知っておくと良いこと:** +> +> - `fetch`のレスポンスはデフォルトではキャッシュされません。ただし、Next.jsはルートを[プリレンダリング](/docs/app/getting-started/partial-prerendering#static-rendering)し、パフォーマンス向上のために出力をキャッシュします。[動的レンダリング](/docs/app/getting-started/partial-prerendering#dynamic-rendering)を選択する場合は、`{ cache: 'no-store' }`オプションを使用してください。[`fetch` APIリファレンス](/docs/app/api-reference/functions/fetch)を参照してください。 +> - 開発中は、`fetch`呼び出しをログに記録して可視性とデバッグを向上させることができます。[`logging` APIリファレンス](/docs/app/api-reference/config/next-config-js/logging)を参照してください。 + +#### ORMまたはデータベースを使用する + +サーバーコンポーネントはサーバーでレンダリングされるため、ORMやデータベースクライアントを使用して安全にデータベースクエリを実行できます。コンポーネントを非同期関数に変更し、呼び出しをawaitします: + +```tsx filename="app/blog/page.tsx" switcher +import { db, posts } from '@/lib/db' + +export default async function Page() { + const allPosts = await db.select().from(posts) + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import { db, posts } from '@/lib/db' + +export default async function Page() { + const allPosts = await db.select().from(posts) + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +### クライアントコンポーネント + +クライアントコンポーネントでデータを取得するには2つの方法があります: + +1. Reactの[`use`フック](https://react.dev/reference/react/use)を使用する +2. [SWR](https://swr.vercel.app/)や[React Query](https://tanstack.com/query/latest)などのコミュニティライブラリを使用する + +#### `use`フックでデータをストリーミングする + +Reactの[`use`フック](https://react.dev/reference/react/use)を使用して、サーバーからクライアントへデータを[ストリーミング](#streaming)できます。まずサーバーコンポーネントでデータを取得し、Promiseをクライアントコンポーネントにpropsとして渡します: + +```tsx filename="app/blog/page.tsx" switcher +import Posts from '@/app/ui/posts +import { Suspense } from 'react' + +export default function Page() { + // データ取得関数をawaitしない + const posts = getPosts() + + return ( + Loading...}> + + + ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import Posts from '@/app/ui/posts +import { Suspense } from 'react' + +export default function Page() { + // データ取得関数をawaitしない + const posts = getPosts() + + return ( + Loading...}> + + + ) +} +``` + +次に、クライアントコンポーネントで`use`フックを使用してPromiseを読み取ります: + +```tsx filename="app/ui/posts.tsx" switcher +'use client' +import { use } from 'react' + +export default function Posts({ + posts, +}: { + posts: Promise<{ id: string; title: string }[]> +}) { + const allPosts = use(posts) + + return ( +
    + {allPosts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/ui/posts.js" switcher +'use client' +import { use } from 'react' + +export default function Posts({ posts }) { + const posts = use(posts) + + return ( +
    + {posts.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +上記の例では、``コンポーネントは[``境界](https://react.dev/reference/react/Suspense)でラップされています。これはPromiseが解決される間、フォールバックが表示されることを意味します。[ストリーミング](#streaming)について詳しく学んでください。 + +#### コミュニティライブラリを使用する + +[SWR](https://swr.vercel.app/)や[React Query](https://tanstack.com/query/latest)などのコミュニティライブラリを使用してクライアントコンポーネントでデータを取得できます。これらのライブラリには、キャッシュ、ストリーミングなどの独自のセマンティクスがあります。例えば、SWRを使用する場合: + +```tsx filename="app/blog/page.tsx" switcher +'use client' +import useSWR from 'swr' + +const fetcher = (url) => fetch(url).then((r) => r.json()) + +export default function BlogPage() { + const { data, error, isLoading } = useSWR( + 'https://api.vercel.app/blog', + fetcher + ) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
    + {data.map((post: { id: string; title: string }) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +'use client' + +import useSWR from 'swr' + +const fetcher = (url) => fetch(url).then((r) => r.json()) + +export default function BlogPage() { + const { data, error, isLoading } = useSWR( + 'https://api.vercel.app/blog', + fetcher + ) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
    + {data.map((post) => ( +
  • {post.title}
  • + ))} +
+ ) +} +``` + +## `React.cache`でリクエストを重複排除 + +重複排除とは、レンダリングパス中に同じリソースに対する重複リクエストを防ぐプロセスです。これにより、異なるコンポーネントで同じデータを取得しながら、データソースへの複数のネットワークリクエストを防ぐことができます。 + +`fetch`を使用している場合、`cache: 'force-cache'`を追加することでリクエストを重複排除できます。これは同じURLとオプションで安全に呼び出し、1つのリクエストのみが行われることを意味します。 + +`fetch`を使用せず、代わりにORMやデータベースを直接使用している場合は、データ取得を[React `cache`](https://react.dev/reference/react/cache)関数でラップできます。 + +```tsx filename="app/lib/data.ts" switcher +import { cache } from 'react' +import { db, posts, eq } from '@/lib/db' + +export const getPost = cache(async (id: string) => { + const post = await db.query.posts.findFirst({ + where: eq(posts.id, parseInt(id)), + }) +}) +``` + +```jsx filename="app/lib/data.js" switcher +import { cache } from 'react' +import { db, posts, eq } from '@/lib/db' +import { notFound } from 'next/navigation' + +export const getPost = cache(async (id) => { + const post = await db.query.posts.findFirst({ + where: eq(posts.id, parseInt(id)), + }) +}) +``` + +## ストリーミング + +> **警告:** 以下の内容は、アプリケーションで[`dynamicIO` configオプション](/docs/app/api-reference/config/next-config-js/dynamicIO)が有効になっていることを前提としています。このフラグはNext.js 15 canaryで導入されました。 + +サーバーコンポーネントで`async/await`を使用すると、Next.jsは[動的レンダリング](/docs/app/getting-started/partial-prerendering#dynamic-rendering)を選択します。これはデータがサーバーで取得され、ユーザーリクエストごとにレンダリングされることを意味します。遅いデータリクエストがある場合、ルート全体のレンダリングがブロックされます。 + +初期読み込み時間とユーザーエクスペリエンスを向上させるために、ストリーミングを使用してページのHTMLを小さなチャンクに分割し、それらのチャンクをサーバーからクライアントに段階的に送信できます。 + +サーバーレンダリングとストリーミングの仕組み + +アプリケーションでストリーミングを実装するには2つの方法があります: + +1. ページを[`loading.js`ファイル](#with-loadingjs)でラップする +2. コンポーネントを[``](#with-suspense)でラップする + +### `loading.js`を使用する + +データが取得されている間に**ページ全体**をストリーミングするには、ページと同じフォルダに`loading.js`ファイルを作成します。例えば、`app/blog/page.js`をストリーミングするには、`app/blog`フォルダ内にファイルを追加します。 + +loading.jsファイルを含むブログフォルダ構造 + +```tsx filename="app/blog/loading.tsx" switcher +export default function Loading() { + // ローディングUIをここで定義 + return
Loading...
+} +``` + +```jsx filename="app/blog/loading.js" switcher +export default function Loading() { + // ローディングUIをここで定義 + return
Loading...
+} +``` + +ナビゲーション時に、ユーザーはレイアウトと[ローディング状態](#creating-meaningful-loading-states)をすぐに確認でき、レンダリングが完了すると新しいコンテンツが自動的に切り替わります。 + +ローディングUI + +内部的には、`loading.js`は`layout.js`内にネストされ、`page.js`ファイルとその下の子を自動的に``境界でラップします。 + +loading.jsの概要 + +このアプローチはルートセグメント(レイアウトとページ)に適していますが、より細かいストリーミングには``を使用できます。 + +### ``を使用する + +``を使用すると、ページのどの部分をストリーミングするかをより細かく制御できます。例えば、``境界の外にあるページコンテンツをすぐに表示し、境界内のブログ投稿リストをストリーミングできます。 + +```tsx filename="app/blog/page.tsx" switcher +import { Suspense } from 'react' +import BlogList from '@/components/BlogList' +import BlogListSkeleton from '@/components/BlogListSkeleton' + +export default function BlogPage() { + return ( +
+ {/* このコンテンツはすぐにクライアントに送信されます */} +
+

ブログへようこそ

+

最新の投稿を以下でお読みください。

+
+
+ {/* 境界でラップされたコンテンツはストリーミングされます */} + }> + + +
+
+ ) +} +``` + +```jsx filename="app/blog/page.js" switcher +import { Suspense } from 'react' +import BlogList from '@/components/BlogList' +import BlogListSkeleton from '@/components/BlogListSkeleton' + +export default function BlogPage() { + return ( +
+ {/* このコンテンツはすぐにクライアントに送信されます */} +
+

ブログへようこそ

+

最新の投稿を以下でお読みください。

+
+
+ {/* 境界でラップされたコンテンツはストリーミングされます */} + }> + + +
+
+ ) +} +``` + +### 意味のあるローディング状態の作成 + +インスタントローディング状態とは、ナビゲーション後にユーザーにすぐに表示されるフォールバックUIです。最適なユーザーエクスペリエンスのために、アプリが応答していることをユーザーが理解できる意味のあるローディング状態を設計することをお勧めします。例えば、スケルトンやスピナー、将来の画面の小さくても意味のある部分(カバー写真、タイトルなど)を使用できます。 + +開発中は、[React Devtools](https://react.dev/learn/react-developer-tools)を使用してコンポーネントのローディング状態をプレビューおよび検査できます。 + +## 例 + +### シーケンシャルなデータ取得 + +シーケンシャルなデータ取得は、ツリー内のネストされたコンポーネントがそれぞれ独自のデータを取得し、リクエストが[重複排除](/docs/app/deep-dive/caching#request-memoization)されない場合に発生し、応答時間が長くなります。 + +シーケンシャルと並列のデータ取得 + +一方の取得が他方の結果に依存するため、このパターンが必要な場合があります。 + +例えば、``コンポーネントは``コンポーネントがデータの取得を終了した後にのみデータの取得を開始します。これは``が`artistID`プロップに依存しているためです: + +```tsx filename="app/artist/[username]/page.tsx" switcher +export default async function Page({ + params, +}: { + params: Promise<{ username: string }> +}) { + const { username } = await params + // アーティスト情報を取得 + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ {/* Playlistsコンポーネントがロード中の間、フォールバックUIを表示 */} + Loading...}> + {/* アーティストIDをPlaylistsコンポーネントに渡す */} + + + + ) +} + +async function Playlists({ artistID }: { artistID: string }) { + // アーティストIDを使用してプレイリストを取得 + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} +``` + +```jsx filename="app/artist/[username]/page.js" switcher +export default async function Page({ params }) { + const { username } = await params + // アーティスト情報を取得 + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ {/* Playlistsコンポーネントがロード中の間、フォールバックUIを表示 */} + Loading...}> + {/* アーティストIDをPlaylistsコンポーネントに渡す */} + + + + ) +} + +async function Playlists({ artistID }) { + // アーティストIDを使用してプレイリストを取得 + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} +``` + +ユーザーエクスペリエンスを向上させるには、データが取得されている間に`fallback`を表示するために[React ``](/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense)を使用してください。これにより[ストリーミング](#streaming)が有効になり、シーケンシャルなデータリクエストによってルート全体がブロックされるのを防ぎます。 + +### 並列データ取得 (Parallel data fetching) + +並列データ取得とは、ルート内のデータリクエストが積極的に開始され、同時に実行されることを指します。 + +デフォルトでは、[レイアウトとページ](/docs/app/getting-started/layouts-and-pages)は並列にレンダリングされます。そのため、各セグメントは可能な限り早くデータの取得を開始します。 + +ただし、_任意の_ コンポーネント内では、複数の `async`/`await` リクエストが連続して配置されている場合、それらは順次実行されます。例えば、以下の例では `getAlbums` は `getArtist` が解決されるまでブロックされます: + +```tsx filename="app/artist/[username]/page.tsx" switcher +import { getArtist, getAlbums } from '@/app/lib/data' + +export default async function Page({ params }) { + // これらのリクエストは順次実行されます + const { username } = await params + const artist = await getArtist(username) + const albums = await getAlbums(username) + return
{artist.name}
+} +``` + +データを使用するコンポーネントの外側でリクエストを定義し、[`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) を使用して一緒に解決することで、並列にリクエストを開始できます: + +```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,23} switcher +import Albums from './albums' + +async function getArtist(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getAlbums(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ + params, +}: { + params: Promise<{ username: string }> +}) { + const { username } = await params + const artistData = getArtist(username) + const albumsData = getAlbums(username) + + // 両方のリクエストを並列に開始 + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +```jsx filename="app/artist/[username]/page.js" highlight={3,8,19} switcher +import Albums from './albums' + +async function getArtist(username) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getAlbums(username) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ params }) { + const { username } = await params + const artistData = getArtist(username) + const albumsData = getAlbums(username) + + // 両方のリクエストを並列に開始 + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +> **補足:** `Promise.all` を使用する場合、1つのリクエストが失敗すると全体の操作が失敗します。これを回避するには、代わりに [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) メソッドを使用できます。 + +### データのプリロード (Preloading data) + +ブロッキングリクエストの前に積極的に呼び出すユーティリティ関数を作成することで、データをプリロードできます。`` は `checkIsAvailable()` 関数に基づいて条件付きでレンダリングされます。 + +`checkIsAvailable()` の前に `preload()` を呼び出すことで、`` のデータ依存関係を積極的に開始できます。`` がレンダリングされる時点では、そのデータはすでに取得されています。 + +```tsx filename="app/item/[id]/page.tsx" switcher +import { getItem } from '@/lib/data' + +export default async function Page({ + params, +}: { + params: Promise<{ id: string }> +}) { + const { id } = await params + // アイテムデータの読み込みを開始 + preload(id) + // 別の非同期タスクを実行 + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} + +export const preload = (id: string) => { + // void は与えられた式を評価し undefined を返します + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export async function Item({ id }: { id: string }) { + const result = await getItem(id) + // ... +} +``` + +```jsx filename="app/item/[id]/page.js" switcher +import { getItem } from '@/lib/data' + +export default async function Page({ params }) { + const { id } = await params + // アイテムデータの読み込みを開始 + preload(id) + // 別の非同期タスクを実行 + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} + +export const preload = (id) => { + // void は与えられた式を評価し undefined を返します + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export async function Item({ id }) { + const result = await getItem(id) + // ... +``` + +さらに、React の [`cache` 関数](https://react.dev/reference/react/cache) と [`server-only` パッケージ](https://www.npmjs.com/package/server-only) を使用して、再利用可能なユーティリティ関数を作成できます。このアプローチでは、データ取得関数をキャッシュし、サーバー上でのみ実行されるようにすることができます。 + +```ts filename="utils/get-item.ts" switcher +import { cache } from 'react' +import 'server-only' +import { getItem } from '@/lib/data' + +export const preload = (id: string) => { + void getItem(id) +} + +export const getItem = cache(async (id: string) => { + // ... +}) +``` + +```js filename="utils/get-item.js" switcher +import { cache } from 'react' +import 'server-only' +import { getItem } from '@/lib/data' + +export const preload = (id) => { + void getItem(id) +} + +export const getItem = cache(async (id) => { + // ... +}) +``` diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx new file mode 100644 index 00000000..dda67d68 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/09-caching-and-revalidating.mdx @@ -0,0 +1,252 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:01:07.019Z +title: データのキャッシュと再検証方法 +nav_title: キャッシュと再検証 +description: アプリケーションでデータをキャッシュおよび再検証する方法を学びます。 +related: + title: APIリファレンス + description: このページで言及されている機能について、APIリファレンスを読んで詳しく学びましょう。 + links: + - app/api-reference/functions/fetch + - app/api-reference/functions/unstable_cache + - app/api-reference/functions/revalidatePath + - app/api-reference/functions/revalidateTag +--- + +キャッシュとは、データフェッチやその他の計算結果を保存する技術で、同じデータへの将来のリクエストを再度作業することなく高速に提供できるようにします。一方、再検証(revalidation)を使用すると、アプリケーション全体を再構築することなくキャッシュエントリを更新できます。 + +Next.jsはキャッシュと再検証を扱うためのいくつかのAPIを提供しています。このガイドでは、それらをいつ、どのように使用するかを説明します。 + +- [`fetch`](#fetch) +- [`unstable_cache`](#unstable_cache) +- [`revalidatePath`](#revalidatepath) +- [`revalidateTag`](#revalidatetag) + +## `fetch` + +デフォルトでは、[`fetch`](/docs/app/api-reference/functions/fetch)リクエストはキャッシュされません。`cache`オプションを`'force-cache'`に設定することで、個々のリクエストをキャッシュできます。 + +```tsx filename="app/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://...', { cache: 'force-cache' }) +} +``` + +```jsx filename="app/page.jsx" switcher +export default async function Page() { + const data = await fetch('https://...', { cache: 'force-cache' }) +} +``` + +> **豆知識**: `fetch`リクエストはデフォルトではキャッシュされませんが、Next.jsは`fetch`リクエストがあるルートを[プリレンダリング](/docs/app/getting-started/partial-prerendering#static-rendering)し、HTMLをキャッシュします。ルートが[動的](/docs/app/getting-started/partial-prerendering#dynamic-rendering)であることを保証したい場合は、[`connection` API](/docs/app/api-reference/functions/connection)を使用してください。 + +`fetch`リクエストで返されたデータを再検証するには、`next.revalidate`オプションを使用できます。 + +```tsx filename="app/page.tsx" switcher +export default async function Page() { + const data = await fetch('https://...', { next: { revalidate: 3600 } }) +} +``` + +```jsx filename="app/page.jsx" switcher +export default async function Page() { + const data = await fetch('https://...', { next: { revalidate: 3600 } }) +} +``` + +これにより、指定された秒数後にデータが再検証されます。 + +詳細については、[`fetch` APIリファレンス](/docs/app/api-reference/functions/fetch)を参照してください。 + +## `unstable_cache` + +`unstable_cache`を使用すると、データベースクエリやその他の非同期関数の結果をキャッシュできます。使用するには、関数を`unstable_cache`でラップします。例: + +```tsx filename="app/lib/data.ts swichter +import { db } from '@/lib/db' +export async function getUserById(id: string) { + return db + .select() + .from(users) + .where(eq(users.id, id)) + .then((res) => res[0]) +} +``` + +```jsx filename="app/lib/data.js" switcher +import { db } from '@/lib/db' + +export async function getUserById(id) { + return db + .select() + .from(users) + .where(eq(users.id, id)) + .then((res) => res[0]) +} +``` + +```tsx filename="app/page.tsx" highlight={2,11,13} switcher +import { unstable_cache } from 'next/cache' +import { getUserById } from '@/app/lib/data' + +export default async function Page({ + params, +}: { + params: Promise<{ userId: string }> +}) { + const { userId } = await params + + const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId] // キャッシュキーにユーザーIDを追加 + ) +} +``` + +```jsx filename="app/page.jsx" highlight={2,7,9} switcher +import { unstable_cache } from 'next/cache'; +import { getUserById } from '@/app/lib/data'; + +export default async function Page({ params } }) { + const { userId } = await params + + const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId] // キャッシュキーにユーザーIDを追加 + ); +} +``` + +この関数は、キャッシュをどのように再検証するかを定義するための第3のオプションオブジェクトを受け入れます。以下のプロパティがあります: + +- `tags`: Next.jsがキャッシュを再検証するために使用するタグの配列 +- `revalidate`: キャッシュを再検証するまでの秒数 + +```tsx filename="app/page.tsx" highlight={6-9} switcher +const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId], + { + tags: ['user'], + revalidate: 3600, + } +) +``` + +```jsx filename="app/page.js" highlight={6-9} switcher +const getCachedUser = unstable_cache( + async () => { + return getUserById(userId) + }, + [userId], + { + tags: ['user'], + revalidate: 3600, + } +) +``` + +詳細については、[`unstable_cache` APIリファレンス](/docs/app/api-reference/functions/unstable_cache)を参照してください。 + +## `revalidateTag` + +`revalidateTag`は、イベント後にタグに基づいてキャッシュエントリを再検証するために使用されます。`fetch`と一緒に使用するには、まず`next.tags`オプションで関数にタグを付けます: + +```tsx filename="app/lib/data.ts" highlight={3-5} switcher +export async function getUserById(id: string) { + const data = await fetch(`https://...`, { + next: { + tags: ['user'], + }, + }) +} +``` + +```jsx filename="app/lib/data.js" highlight={3-5} switcher +export async function getUserById(id) { + const data = await fetch(`https://...`, { + next: { + tags: ['user'], + }, + }) +} +``` + +または、`unstable_cache`関数に`tags`オプションをマークすることもできます: + +```tsx filename="app/lib/data.ts" highlight={6-8} switcher +export const getUserById = unstable_cache( + async (id: string) => { + return db.query.users.findFirst({ where: eq(users.id, id) }) + }, + ['user'], // 変数がパラメータとして渡されない場合に必要 + { + tags: ['user'], + } +) +``` + +```jsx filename="app/lib/data.js" highlight={6-8} switcher +export const getUserById = unstable_cache( + async (id) => { + return db.query.users.findFirst({ where: eq(users.id, id) }) + }, + ['user'], // 変数がパラメータとして渡されない場合に必要 + { + tags: ['user'], + } +) +``` + +次に、[Route Handler](/docs/app/api-reference/file-conventions/route)またはServer Actionで`revalidateTag`を呼び出します: + +```tsx filename="app/lib/actions.ts" highlight={1} switcher +import { revalidateTag } from 'next/cache' + +export async function updateUser(id: string) { + // データを変更 + revalidateTag('user') +} +``` + +```jsx filename="app/lib/actions.js" highlight={1} switcher +import { revalidateTag } from 'next/cache' + +export async function updateUser(id) { + // データを変更 + revalidateTag('user') +} +``` + +同じタグを複数の関数で再利用して、一度にすべてを再検証できます。 + +詳細については、[`revalidateTag` APIリファレンス](/docs/app/api-reference/functions/revalidateTag)を参照してください。 + +## `revalidatePath` + +`revalidatePath`は、イベント後にルートを再検証するために使用されます。使用するには、[Route Handler](/docs/app/api-reference/file-conventions/route)またはServer Actionで呼び出します: + +```tsx filename="app/lib/actions.ts" highlight={1} switcher +import { revalidatePath } from 'next/cache' + +export async function updateUser(id: string) { + // データを変更 + revalidatePath('/profile') +``` + +```jsx filename="app/lib/actions.js" highlight={1} switcher +import { revalidatePath } from 'next/cache' + +export async function updateUser(id) { + // データを変更 + revalidatePath('/profile') +``` + +詳細については、[`revalidatePath` APIリファレンス](/docs/app/api-reference/functions/revalidatePath)を参照してください。 \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/10-updating-data.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/10-updating-data.mdx new file mode 100644 index 00000000..d73d4b97 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/10-updating-data.mdx @@ -0,0 +1,352 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:01:14.188Z +title: データの更新方法 +nav_title: データ更新 +description: Next.jsアプリケーションでデータを更新する方法について学びましょう。 +related: + title: APIリファレンス + description: このページで紹介されている機能について詳しく知るには、APIリファレンスをお読みください。 + links: + - app/api-reference/functions/revalidatePath + - app/api-reference/functions/revalidateTag + - app/api-reference/functions/redirect +--- + +Next.jsではReactの[サーバー関数 (Server Functions)](https://react.dev/reference/rsc/server-functions)を使用してデータを更新できます。このページでは[サーバー関数の作成](#creating-server-functions)と[呼び出し](#invoking-server-functions)方法について説明します。 + +## サーバー関数 + +サーバー関数とは、サーバー上で実行される非同期関数です。サーバー関数はクライアントからネットワークリクエスト経由で呼び出されるため、本質的に非同期です。`action`の一部として呼び出される場合、**サーバーアクション (Server Actions)** とも呼ばれます。 + +慣例として、`action`は`startTransition`に渡される非同期関数です。サーバー関数は以下の場合に自動的に`startTransition`でラップされます: + +- `
`の`action`プロパティに渡された場合 +- ` +} +``` + +```jsx filename="app/ui/button.js" switcher +'use client' + +import { createPost } from '@/app/actions' + +export function Button() { + return +} +``` + +## サーバー関数の呼び出し + +サーバー関数を呼び出す主な方法は2つあります: + +1. サーバー/クライアントコンポーネント内の[フォーム](#forms) +2. クライアントコンポーネント内の[イベントハンドラー](#event-handlers) + +### フォーム + +ReactはHTMLの[``](https://react.dev/reference/react-dom/components/form)要素を拡張し、HTMLの`action`プロパティでサーバー関数を呼び出せるようにしています。 + +フォーム内で呼び出された場合、関数は自動的に[`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData)オブジェクトを受け取ります。ネイティブの[`FormData`メソッド](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods)を使用してデータを抽出できます: + +```tsx filename="app/ui/form.tsx" switcher +import { createPost } from '@/app/actions' + +export function Form() { + return ( + + + + +
+ ) +} +``` + +```jsx filename="app/ui/form.js" switcher +import { createPost } from '@/app/actions' + +export function Form() { + return ( +
+ + + +
+ ) +} +``` + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createPost(formData: FormData) { + const title = formData.get('title') + const content = formData.get('content') + + // データを更新 + // キャッシュを再検証 +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function createPost(formData) { + const title = formData.get('title') + const content = formData.get('content') + + // データを更新 + // キャッシュを再検証 +} +``` + +> **豆知識**:`action`プロパティに渡された場合、サーバー関数は_サーバーアクション_とも呼ばれます。 + +### イベントハンドラー + +`onClick`などのイベントハンドラーを使用して、クライアントコンポーネント内でサーバー関数を呼び出すことができます。 + +```tsx filename="app/like-button.tsx" switcher +'use client' + +import { incrementLike } from './actions' +import { useState } from 'react' + +export default function LikeButton({ initialLikes }: { initialLikes: number }) { + const [likes, setLikes] = useState(initialLikes) + + return ( + <> +

総いいね数: {likes}

+ + + ) +} +``` + +```jsx filename="app/like-button.js" switcher +'use client' + +import { incrementLike } from './actions' +import { useState } from 'react' + +export default function LikeButton({ initialLikes }) { + const [likes, setLikes] = useState(initialLikes) + + return ( + <> +

総いいね数: {likes}

+ + + ) +} +``` + +## 例 + +### 保留状態の表示 + +サーバー関数の実行中、Reactの[`useActionState`](https://react.dev/reference/react/useActionState)フックを使用してローディングインジケーターを表示できます。このフックは`pending`ブール値を返します: + +```tsx filename="app/ui/button.tsx" switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' +import { LoadingSpinner } from '@/app/ui/loading-spinner' + +export function Button() { + const [state, action, pending] = useActionState(createPost, false) + + return ( + + ) +} +``` + +```jsx filename="app/ui/button.js" switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' +import { LoadingSpinner } from '@/app/ui/loading-spinner' + +export function Button() { + const [state, action, pending] = useActionState(createPost, false) + + return ( + + ) +} +``` + +### キャッシュの再検証 + +更新を実行した後、サーバー関数内で[`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)または[`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)を呼び出すことでNext.jsのキャッシュを再検証し、更新されたデータを表示できます: + +```ts filename="app/lib/actions.ts" switcher +import { revalidatePath } from 'next/cache' + +export async function createPost(formData: FormData) { + 'use server' + // データを更新 + // ... + + revalidatePath('/posts') +} +``` + +```js filename="app/actions.js" switcher +import { revalidatePath } from 'next/cache' + +export async function createPost(formData) { + 'use server' + // データを更新 + // ... + revalidatePath('/posts') +} +``` + +### リダイレクト + +更新後にユーザーを別のページにリダイレクトしたい場合があります。サーバー関数内で[`redirect`](/docs/app/api-reference/functions/redirect)を呼び出すことでこれを行えます: + +```ts filename="app/lib/actions.ts" switcher +'use server' + +import { redirect } from 'next/navigation' + +export async function createPost(formData: FormData) { + // データを更新 + // ... + + redirect('/posts') +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { redirect } from 'next/navigation' + +export async function createPost(formData) { + // データを更新 + // ... + + redirect('/posts') +} +``` \ No newline at end of file diff --git a/apps/docs/content/ja/docs/01-app/01-getting-started/11-error-handling.mdx b/apps/docs/content/ja/docs/01-app/01-getting-started/11-error-handling.mdx new file mode 100644 index 00000000..58929656 --- /dev/null +++ b/apps/docs/content/ja/docs/01-app/01-getting-started/11-error-handling.mdx @@ -0,0 +1,317 @@ +--- +source-updated-at: 2025-06-01T01:32:20.000Z +translation-updated-at: 2025-06-02T20:01:06.286Z +title: エラーハンドリング方法 +nav_title: エラーハンドリング +description: 予期されるエラーの表示方法とキャッチされない例外の処理方法について学びます。 +related: + title: APIリファレンス + description: このページで言及されている機能について、APIリファレンスを読んでさらに学びましょう。 + links: + - app/api-reference/functions/redirect + - app/api-reference/file-conventions/error + - app/api-reference/functions/not-found + - app/api-reference/file-conventions/not-found +--- + +エラーは[予期されるエラー](#handling-expected-errors)と[キャッチされない例外](#handling-uncaught-exceptions)の2つのカテゴリに分けられます。このページでは、Next.jsアプリケーションでこれらのエラーを処理する方法を説明します。 + +## 予期されるエラーの処理 + +予期されるエラーとは、[サーバーサイドフォームバリデーション](/docs/app/guides/forms)や失敗したリクエストなど、アプリケーションの通常の操作中に発生する可能性のあるエラーです。これらのエラーは明示的に処理され、クライアントに返されるべきです。 + +### サーバー関数 + +[サーバー関数](https://react.dev/reference/rsc/server-functions)内で予期されるエラーを処理するには、[`useActionState`](https://react.dev/reference/react/useActionState)フックを使用できます。 + +これらのエラーに対しては、`try`/`catch`ブロックを使用したりエラーをスローしたりするのではなく、予期されるエラーを戻り値としてモデル化します。 + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createPost(prevState: any, formData: FormData) { + const title = formData.get('title') + const content = formData.get('content') + + const res = await fetch('https://api.vercel.app/posts', { + method: 'POST', + body: { title, content }, + }) + const json = await res.json() + + if (!res.ok) { + return { message: 'Failed to create post' } + } +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function createPost(prevState, formData) { + const title = formData.get('title') + const content = formData.get('content') + + const res = await fetch('https://api.vercel.app/posts', { + method: 'POST', + body: { title, content }, + }) + const json = await res.json() + + if (!res.ok) { + return { message: 'Failed to create post' } + } +} +``` + +アクションを`useActionState`フックに渡し、返された`state`を使用してエラーメッセージを表示できます。 + +```tsx filename="app/ui/form.tsx" highlight={11,19} switcher +'use client' + +import { useActionState } from 'react' +import { createPost } from '@/app/actions' + +const initialState = { + message: '', +} + +export function Form() { + const [state, formAction, pending] = useActionState(createPost, initialState) + + return ( +
+ + + +