とほほのNext.js入門

トップ > Next.js

目次

Next.jsとは

ソースファイル

本ページで紹介するサンプルコードは下記からダウンロードできます。

インストール

下記を参考にインストールします。

下記のものが必要です。

「自動インストール方式」と「手動インストール方式」がありますが、ここではシンプルな手動インストール方式を説明します。

$ mkdir myapp
$ cd myapp
$ npm install next@latest react@latest react-dom@latest typescript@latest @types/react @types/node
$ mkdir app

./package.json に下記を追加します。これで npm run dev や npm run build などのコマンドを使用できるようになります。

./package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
      :

./app/layout.tsx を作成します。レイアウトはページの外枠を定義します。

./app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  )
}

./app/page.tsx を作成します。レイアウトの中に埋め込むページを定義します。

./app/page.tsx
export default function Page() {
  return <h1>Top Page</h1>
}

開発用簡易サーバを起動します。

$ npm run dev

ブラウザから http://{サーバアドレス}:3000/ にアクセスして「Top Page」が表示されれば成功です。

本番サーバを起動するには build してから start します。

$ npm run build
$ npm run start

フォルダ・ファイル構成

Next.js のおおまかなフォルダ構成は下記となります。

myapp/      : プロジェクト名。好きな名前を命名。
  app/      : App Routerを使用する場合のメインフォルダ
  pages/    : Pages Routerを使用する場合のメインフォルダ(今回は使用しません)
  public/   : スタティックファイルを格納するフォルダ(後述します)
  src/      : オプショナルなアプリケーションソースを格納するフォルダ(今回は使用しません)

myapp の下には必要に応じて下記などのファイルを置いていきます。詳細は Next.js の マニュアル を参照してください。

next.config.js       Configuration file for Next.js
package.json         Project dependencies and scripts
instrumentation.ts   OpenTelemetry and Instrumentation file
middleware.ts        Next.js request middleware
.env                 Environment variables
.env.local           Local environment variables
.env.production      Production environment variables
.env.development     Development environment variables
.eslintrc.json       Configuration file for ESLint
.gitignore           Git files and folders to ignore
next-env.d.ts        TypeScript declaration file for Next.js
tsconfig.json        Configuration file for TypeScript
jsconfig.json        Configuration file for JavaScript

./myapp/app の下には必要に応じて下記などのファイルを作成していきます。拡張子は JavaScript(.js), JavaScript XML(.jsx), TypeScript(.tsx) を意味します。本書では主に .jsx を使用します。

layout        .js .jsx .tsx    Layout
template      .js .jsx .tsx    Re-rendered layout
page          .js .jsx .tsx    Page
loading       .js .jsx .tsx    Loading UI
not-found     .js .jsx .tsx    Not found UI
error         .js .jsx .tsx    Error UI
global-error  .js .jsx .tsx    Global error UI
route         .js .ts          API endpoint
default       .js .jsx .tsx    Parallel route fallback page

App Router と Pages Router

Next.js では App Router と Pages Router の二つの方式をサポートしており、どちらを選ぶかによって開発方式が大きく異なります。./app を使用するのが App Router で、./pages を使用するのが Pages Router です。App Router は Next.js v13.4 から導入された新しい開発方式で、Pages Router の進化版とも言えることから、今後は App Router 方式が主流になると思われます。本書では App Router についてのみ説明していきます。

基本ファイル

ページ(page.tsx)

./app/{パス名}/page.tsx を作成すると、ブラウザから http://{サーバ名}/{パス名} でアクセスできます。./app/{パス名1}/{パス名2}/page.tsx を作成すると、ブラウザから http://{サーバ名}/{パス名1}/{パス名2} でアクセスできます。試しに ./app/page-test フォルダを作成して、配下に ./app/page-test/page.tsx を作成してみます。

./app/page-test/page.tsx
export default function Page() {
  return <h1>Page Test</h1>
}

ブラウザから http://~/page-test にアクセスして Page Test が表示されれば成功です。

レイアウト(layout.tsx)

./app/layout.tsx で指定したレイアウトはすべてのページに適用されます。./app/xxx/layout.tsx で指定したレイアウトは、xxx 配下のすべてのページに適用されます。./app/layout-test フォルダを作成して layout.tsx と page.tsx を作成してみます。

./app/layout-test/layout.tsx
export default function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <hr />
      <div>{children}</div>
      <hr />
    </>
  )
}
./app/layout-test/page.tsx
export default function Page() {
  return <h1>Layout Test</h1>
}

http://~/layout-test を開いて Layout Test の上下に線が引かれていれば成功です。これは、http://~/layout-test/page1/page2 のように階層が深くなっても反映されます。

テンプレート(template.tsx)

テンプレート(Template)はレイアウト(Layout)とほぼ同じ機能を持ちます。レイアウトは画面が遷移してもDOM要素やステートが維持されるのに対し、テンプレートは遷移の度に新しいDOM要素が作成され、ステートがリセットされます。画面遷移時にレイアウトに埋め込んだフォームを初期化したい場合やアニメーションを表示したい場合など、レイアウトの代わりにテンプレートが使用されます。./app/template-test フォルダを作成して template.tsx と page.tsx を作成してみます。

./app/template-test/template.tsx
export default function Template({
  children
}: {
  children: React.ReactNode
}) {
  return <div>{children}</div>
}
./app/template-test/page.tsx
export default function Page() {
  return <h1>Template Test</h1>
}

http://~/template-test にアクセスして Template Test が表示されれば成功です。

ローディング(loading.tsx)

時間のかかるコンポーネントを表示する際に非同期で呼び出しを行い、待っている間ローディングアイコン等を表示することができます。まず、./app/loading-test フォルダを作成し、ローディング中であることを示す loading.tsx 部品を作成します。

./app/loading-test/loading.tsx
export default function Loading() {
  return <div>Loading...</div>
}

次に時間のかかるコンポーネント WaitComponent を作成します。

./app/loading-test/wait.tsx
function aWait() {
  return new Promise(callback => { setTimeout(callback, 3000) })
}
export default async function WaitComponent() {
  await aWait()
  return <div>Finished!</div>
}

これらを呼び出す page.tsx を作成します。時間のかかるコンポーネントを <Suspense>~</Suspense> で囲み、fallback に Loading を指定します。<Suspense> を使用することにより、時間のかかる処理が終わるまでページ全体のレンダリングが待たされることなく、個々の <Suspense> を個別に非同期に呼び出すことができます。また、読み込み中は fallback で指定したローディング部品を表示します。

./app/loading-test/page.tsx
import { Suspense } from 'react'
import Loading from './loading'
import WaitComponent from './wait'

export default function Page() {
  return (
    <div>
      <h1>Loading Test</h1>
      <Suspense fallback={<Loading />}>
        <WaitComponent />
      </Suspense>
    </div>
  )
}

http://~/loading-test にアクセスすると数秒間 Loading... が表示されて Finished! に変われば成功です。

Not Foundページ(not-found.tsx)

./app 直下に not-found.tsx を作成すると、404 Not Found エラーが発生した際のページを作成することができます。

./app/not-found.tsx
export default function NotFound() {
  return <h1>Not Found</h1>
}

存在しないページにアクセスして Not Found が表示されれば成功です。

エラーページ(error.tsx)

下記のようなエラーページを ./app/error-test/error.tsx として用意します。

./app/error-test/error.tsx
'use client'
import { useEffect } from 'react'

export default function Error({error, reset,}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {console.error(error)}, [error])
  return (
    <>
      <h1>Error</h1>
      <button onClick={() => reset()}>Try again</button>
    </>
  )
}

./app/error-test/page.tsx の中でエラーを発生させてみます。

./app/error-test/page.tsx
export default function Page({params, searchParams}) {
  const error = searchParams?.error
  if (error) {
    throw new Error('ERROR!!!!!')
  }
  return <h1>Error Test</h1>
}

http://~/error-test?error=1 にアクセスしてエラーページが表示されれば成功です。

グローバルエラー(global-error.tsx)

error.tsx では page.tsx で発生したエラーを捕捉することはできますが、layout.tsx や template.tsx で発生したエラーを捕捉することができません。これらを捕捉するには global-error.tsx を作成します。

./app/global-error.tsx
'use client'

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Global Error</h2>
        <div>{error.message}</div>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  )
}

スタイリング

CSSモジュール

グローバルのCSSを設定するには下記の様にします。

./app/global.css
body {
  background-color: #ddf;
}
./app/layout.tsx
import './global.css'
  :

クラス名を指定したCSSを適用するには下記の様にします。

./app/css-test/test1.module.css
.test1 { color: red; }
./app/css-test/test2.module.css
.test2 { color: blue; }
./app/css-test/page.tsx
import style1 from './test1.module.css'
import style2 from './test2.module.css'

export default function Page() {
  return (
    <>
      <h1>CSS Test</h1>
      <div className={style1.test1}>Test1 message.</div>
      <div className={style2.test2}>Test2 message.</div>
    </>
  )
}

http://~/css-test を開いて背景が薄青く、赤いメッセージと青いメッセージが表示されていれば成功です。

Tailwind CSS

Tailwind CSS を使用することもできます。Tailwind CSS を使用する場合は、すてのデフォルトスタイルがキャンセルされるため、すべての部品のスタイルを再構築する必要があります。

$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p

./tailwind.config.js に下記を追加します。

./tailwind.config.js
module.exports = {
  content: [ './app/**/*.{js,ts,jsx,tsx,mdx}' ],
  theme: {
    extend: {},
  },
  plugins: [],
}

./app/tailwind-test/style.css を作成します。

./app/tailwind-test/style.css
@tailwind base;
@tailwind components;
@tailwind utilities;

これを ./app/tailwind-test/page.tsx から読み込みます。

./app/tailwind-test/page.tsx
import './style.css'

export default function Page() {
  return <h1 className="text-6xl font-bold text-blue-600">Tailwind Test</h1>
}

http://~/tailwind-test を開いて Tailwind Test の文字が青くなっていれば成功です。反映されない場合は簡易サーバ(npm run dev)を再起動してください。

Sass

スタイルシートとして Sass を使用することもできます。

$ npm install --save-dev sass
./app/sass-test/style.scss
$body-background-color: #dfd;

body {
  background-color: $body-background-color;
}
./app/sass-test/page.tsx
import './style.scss'

export default function Page() {
  return <h1>Sass Test</h1>
}

http://~/sass-test にアクセスして背景が緑になったら成功です。

フォルダ名

プライベートフォルダ(_xxx)

./app 配下にディレクトリを作成しても page.tsx や route.ts を配置しない限りはアクセスされることはありませんが、アンダーバー(_)で始まるフォルダはプライベートフォルダと認識され、たとえ page.tsx や route.ts が配置されてもアクセスされることはありません。

./app/
  xxx/page.tsx
  yyy/route.ts
  _lib/page.tsx     # アクセスされない

./app/_lib/page.tsx を作成します。

./app/_lib/page.tsx
export default function Page() {
  return <h1>Private Folder Test</h1>
}

http://~/_lib にアクセスしても page.tsx は表示されず、Not Found になることが確認できます。

ルートグループ((xxx))

./app/(...) の名前を持つフォルダはルートグループを形成します。例えば下記のフォルダ構成では、member1~member3 は一つの group1 グループに含まれますが、http://~/member1, http://~/member2, http://~/member3 としてアクセスすることができます。グループはレイアウトやテンプレートを共有することができます。

./app/
   (gorup1)/
      layout.tsz
      member1/page.tsx
      member2/page.tsx
      member3/page.tsx

まず、./app/(group1) フォルダを作成します。( や ) がシェルのメタ文字として解釈されないようにパス名を '...' で囲みます。

$ mkdir './app/(group1)'
./app/(group1)/layout.tsx
export default function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <h1>(group1)</h1>
      <div>{children}</div>
    </>
  )
}
./app/(group1)/member1/page.tsx
export default function Page() {
  return <h2>Member 1</h2>
}

http://~/member1 にアクセスして (group1) と Member 1 が表示されれば成功です。

ダイナミックルーティング([xxx])

http://~/blog/{slug} の様に URL でブログIDなどのパラメータを受け取りたい場合があります。./app/blog/[slug] という名前のフォルダを作成します。

$ mkdir -p './app/blog/[slug]'

./app/blog/[slug]/page.tsx ファイルを作成します。

./app/blog/[slug]/page.tsx
export default function Page({ params }: { params: { slug: string } }) {
  return <h1>Blog: {params.slug}</h1>
}

http://~/blog/123 にアクセスして Blog: 123 と表示されれば成功です。

ダイナミックルーティング:階層化([...xxx])

http://~/blog2/{slug1}/{slug2}/{slug3} のように階層的なパラメータを受け取るには、./app/blog2/[...slugs]/page.tsx ファイルを作成します。

$ mkdir -p './app/blog2/[...slugs]'
./app/blog2/[...slugs]/page.tsx
export default function Page({ params }: { params: { slugs: string[] } }) {
  return <h1>Blog: {params.slugs.join(', ')}</h1>
}

http://~/blog2/123/456 にアクセスして Blog: 123, 456 と表示されれば成功です。

ダイナミックルーティング:パラメータ無し対応([[...xxx]])

上記の例では /blog2/123 や /blog2/456 にはアクセスできても /blog2 にアクセスすると 404 Not Found となってしまいます。/blog2 にもアクセスできるようにするには、[[...slugs]] の様に括弧を二重にします。下記の例では /blog3 にアクセスすると (None) を表示します。

$ mkdir -p './app/blog3/[[...slugs]]'
./app/blog3/[[...slugs]]/page.tsx
export default function Page({ params }: { params: { slugs: string[] } }) {
  if (params.slugs) {
    return <h1>Blog: {params.slugs.join(', ')}</h1>
  } else {
    return <h1>Blog: (None)</h1>
  }
}

http://~/blog3 にアクセスして Blog: (Node) が、http://~/blog3/123/456 にアクセスして Blog: 123, 456 と表示されれば成功です。

ダイナミックルーティング:複数パラメータ([xxx]/[yyy])

http://~/blog4/{カテゴリ}/{slug} を受け取るには ./app/blog4/[category]/[slug]/page.tsx ファイルを作成します。

$ mkdir -p './app/blog4/[category]/[slug]'
./app/blog4/[category]/[slug]/page.tsx
export default function Page({ params }: { params: { category: string, slug: string } }) {
  return <h1>Blog: {params.category} / {params.slug}</h1>
}

http://~/blog4/movie/123 にアクセスして Blog: movie / 123 と表示されれば成功です。

平行ルーティング(@xxx)

Next.js 13 からサポートされた機能で、ひとつのレイアウトの中で複数の子ノードを並列に描画することができます。時間のかかる子ノードを複数同時に描画したい場合、子ノードを @ で始まるフォルダ名で作成します。

./app/
   parallel-test/
      layout.tsx
      page.tsx
      @node1/page.tsx
      @node2/page.tsx
./app/parallel-test/layout.tsx
export default function Layout(props) {
  return (
    <div>
      <div>{props.children}</div>
      <div>{props.node1}</div>
      <div>{props.node2}</div>
    </div>
  )
}
./app/parallel-test/page.tsx
export default function Page() {
  return <h1>Parallel Test</h1>
}
./app/parallel-test/@node1/page.tsx
export default function Page() {
  return <h2>Node1</h2>
}
./app/parallel-test/@node2/page.tsx
export default function Page() {
  return <h2>Node2</h2>
}

http://~/parallel-test にアクセスして Parallel Test, Node1, Node2 が表示されていれば成功です。

横取りルーティング((.)xxx, (..)xxx, (...)xxx)

Next.js 13.3 からサポートされた機能で、現在のページの上に、別のページをモーダルで表示することができます。写真をクリックするとその写真がモーダルで拡大表示されるケースなどで利用されます。少々コード量が多いので本家が提供しているサンプルを紹介します。

ソースサンプルは下記のファイルから構成されます。

./app/layout.tsx
./app/page.tsx
./app/default.tsx
./app/global.css
./app/photos/[id]/page.tsx
./app/@modal/default.tsx
./app/@modal/(.)photos/[id]/modal.tsx
./app/@modal/(.)photos/[id]/page.tsx

(.) などは下記の意味を持ちます。

(.)      同一階層のセグメントにマッチ
(..)     ひとつ上のディレクトリのセグメントにマッチ
(..)(..) ふたつ上のディレクトリのセグメントにマッチ
(...)    ルート(./app)ディレクトリにマッチ

コンポーネント(Components)

オリジナルコンポーネント(Original Component)

React なので部品をコンポーネント化することができます。テストメッセージを表示する部品を作成してみます。ここらへんの詳細は React を参照してください。

./app/component-test/testMessage.tsx
export default function TestMessage() {
  return <div>Test message.</div>
}
./app/component-test/page.tsx
import TestMessage from './testMessage'

export default function Page() {
  return (
    <>
      <h1>Component Test</h1>
      <TestMessage />
    </>
  )
}

http://~/component-test を開いて Test message. と表示されていれば成功です。

Page Test へのリンクを張ってみます。<a> で張る方法と <Link> で張る方法があります。

./app/link-test/page.tsx
import Link from 'next/link'

export default function Page() {
  return (
    <>
      <h1>Link Test</h1>
      <ul>
      <li><a href="page-test">Page Test</a></li>
      <li><Link href="page-test">Page Test</Link></li>
      </ul>
    </>
  )
}

http://~/link-test を表示して Page Test へのリンクが2つ表示されていれば成功です。<a> で張った場合は遷移時に画面の再読込が行われるのに対して、<Link> で張った場合は再読み込み無しに描画されます。

リンク(useRouter())

useRouter() を用いることで、ユーザクリック時ではなく、プログラム中から任意のタイミングでリンク先にジャンプすることができます。

./app/use-router-test/page.tsx
'use client'
import { useRouter } from 'next/navigation'

export default function Page() {
  const router = useRouter()
  return (
    <div>
      <h1>useRouter Test</h1>
      <button onClick={() => router.push('/')}>Top</button>
    </div>
  )
}

http://~/use-route-test にアクセスしてボタンをクリックすると Top ページに遷移すれば成功です。

フォント(Font)

Googleフォントを利用することができます。Google の Roboto フォントを使用する例を下記に示します。指定可能なパラメータは Font を参照してください。

./app/font-test/page.tsx
import { Roboto } from 'next/font/google'
const Roboto900 = Roboto({ weight:'900', preload:false })

export default function Page() {
  return (
    <>
      <h1>Google Roboto Font Test</h1>
      <h1 className={Roboto900.className}>Google Roboto Font Test</h1>
    </>
  )
}

http://~/font-test にアクセスしてフォントの異なるタイトルが表示されていれば成功です。

イメージ(Image)

イメージコンポーネント <Image> を使用することができます。指定可能なパラメータは <Image> を参照してください。<img> とあまり変わらないように見えますが、配置されるサイズに応じて適切なサイズにトリミングしてくれます。

./app/image-test/page.tsx
import Image from 'next/image'

export default function Page() {
  return (
    <>
      <h1>Image Test</h1>
      <Image src="/image/sample.png" width={58} height={40} alt="Sample Image" />
    </>
  )
}

./public/image/sample.png に任意の PNGファイルを配置してください。http://~/image-test にアクセスすると画像が表示されれば成功です。

パス参照

絶対パスインポート(Absolute imports)

./components/button.tsx 部品を作成し、これを ./app/*/page.tsx から import する場合、'../../components/button' と記述する必要がありますが、tsconfig.json または jsconfig.json に baseUrl を指定すると、'components/button' として参照できるようになります。npm run dev を再起動する必要があります。

./tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
      :
./components/button.tsx
export default function Button() {
  return <button>Click me</button>
}

./app/absolute-import-test/page.tsx
import Button from 'components/button'

export default function Page() {
  return (
    <>
      <h1>Absolute Import Test</h1>
      <Button />
    </>
  )
}

コンフィグを書き換えたので npm run dev を再起動した後、http://~/absolute-import-test にアクセスし、Click me ボタンが表示されれば成功です。

モジュールエイリアス(Module aliases)

./tsconfig.json または ./jsconfig.json に paths を指定するとパス名のエイリアスを使用することができるようになります。

./tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@alias1/*": ["components/*"] },
      :
./app/module-alias-test/page.tsx
import Button from '@alias1/button'

export default function Page() {
  return (
    <>
      <h1>Module Alias Test</h1>
      <Button />
    </>
  )
}

コンフィグを書き換えたので npm run dev を再起動した後、http://~/module-alias-test にアクセスし、Click me ボタンが表示されれば成功です。

データ操作

フェッチ(Fetch)

fetch()を用いて他のWebにアクセスすることができます。fetch() に関する詳細は fetch() を参照してください。

./app/fetch-test/page.tsx
export default async function Page() {
  const response = await fetch('https://www.tohoho-web.com/cgi/client-ip-address.cgi')
  const body = await response.text()
  return (
    <>
      <h1>Fetch Test</h1>
      <div>{body}</div>
    </>
  )
}

http://~/fetch-test にアクセスし、IPアドレスが表示されていれば成功です。

サーバサイドコンポーネント(Server Side Component)

Next.js では簡単にサーバサイドの実行とクライアントサイドの実行を切り替えることができます。デフォルトはサーバサイドです。

./app/server-side-test/page.tsx
export default function Page() {
  console.log('Server Side Test')
  return <h1>Server Side Test</h1>
}

http://~/server-side-test にアクセスするとサーバ側のコンソールに Server Side Test が表示されます。

クライアントサイドコンポーネント(Client Side Component)

'use client' を指定するとクライアントサイドになります。

./app/client-side-test/page.tsx
'use client'
export default function Page() {
  console.log('Client Side Test')
  return <h1>Client Side Test</h1>
}

http://~/client-side-test にアクセスするとクライアント側の開発ツールのコンソールに Client Side Test が表示されます。

サーバアクション(Server action)

また、下記の様に 'use server' を指定したサーバサイドアクションを、クライアント側のフォームから簡単に呼び出すことができます。下記の例ではクライアントのフォームに入力したメッセージがサーバに送られ、サーバのコンソールに出力されます。

./app/server-action-test/page.tsx
export default function Page() {
  async function action_test(form) {
    'use server'
    const message = form.get('message')
    console.log({message})
  }
  return (
    <>
      <h1>Server Action Test</h1>
      <form action={action_test}>
        <input type="text" name="message" />
        <button type="submit">OK</button>
      </form>
    </>
  )
}

http://~/server-action-test にアクセスしてフォームに入力して [OK] を押すと、サーバ側にメッセージが表示されれば成功です。

デフォルト値(defaultValue)

フォーム部品にデフォルト値を設定するには value="..." ではなく defaultValue="..." を指定します。

<input type="text" name="message" defaultValue="xxx" />
フォームとアクション(Form & Action)

サーバで処理した結果を返すには useFormState() を用います。クライアントでフォームを表示し、サブミット時にサーバアクションを呼び出します。今回は常に成功するメソッドとしていますが、結果を Form State を経由して画面に返却しています。

./app/login-test/login.tsx
'use server'
export async function loginAction(state: any, formData: FormData) {
  const user_id = formData.get('user_id')
  const password = formData.get('password')
  console.log({user_id, password})
  return {
    message: 'OK',
  }
}
./app/login-test/page.tsx
'use client'
import { useFormState } from 'react-dom'
import { loginAction } from './login'
const initialState = { message: '' }

export default function Page() {
  const [state, formAction] = useFormState(loginAction, initialState)
  return (
    <>
      <h1>Login Test</h1>
      <form action={formAction}>
        <div><input type="text" name="user_id" placeholder="User ID" /></div>
        <div><input type="password" name="password" placeholder="Password" /></div>
        <button type="submit">Login</button>
        <p>{state?.message}</p>
      </form>
    </>
  )
}

http://~/login-test にアクセスして、User ID と Password に何かを入力して Login ボタンを押し、OK と表示されれば成功です。

API

ルーティング(route.ts)

route.ts は REST-API を作成するのに便利な機能です。下記の内容で ./app/api/books/route.ts を作成します。

./app/api/books/route.ts
var books = [
  { title: '吾輩は猫である', author: '夏目漱石' },
]

export async function GET(request: Request) {
  return Response.json({ books: books }, { status: 200 })
}

export async function POST(request: Request) {
  var book = await request.json()
  books.push({ title: book.title, author: book.author })
  return Response.json({ result: 'OK' }, { status: 200 })
}

curl でこれらを呼び出してみます。

$ curl http://localhost:3000/api/books
{"books":[{"title":"吾輩は猫である","author":"夏目漱石"}]}
$ curl -X POST http://localhost:3000/api/books -d '{"title":"銀河鉄道の夜","author":"宮沢賢治"}'
{"result":"OK"}
$ curl http://localhost:3000/api/books
{"books":[{"title":"吾輩は猫である","author":"夏目漱石"},{"title":"銀河鉄道の夜","author":"宮沢賢治"}]}

上記の例では入出力に JavaScript 標準の Response/Response を使用していますが、Next.js で拡張した NextRequest/NextResponse を使用することもできます。

ヘッダー(Headers)

リクエストヘッダを読み込むには headers() を使用します。レスポンスヘッダを指定するには Response() の第2引数オブジェクトに headers を指定します。詳細は headers を参照してください。

./app/api/header-test/route.ts
import { headers } from 'next/headers'
export async function GET(request: Request) {
  const user_agent = headers().get('User-Agent')
  return new Response('OK', {status: 200, headers: {'X-Test-Header': user_agent}})
}

curl で http://~/api/header-test を呼び出して、X-Test-Header が返却されれば成功です。

$ curl -i http://localhost:3000/api/header-test
HTTP/1.1 200 OK
x-test-header: curl/7.76.1
  :
クッキー(Cookies)

Cookie を参照するには cookies() から get() して value を参照します。設定するには Response() の第2引数オブジェクトの headers に Set-Cookie を指定します。詳細は cookies を参照してください。

./app/api/cookie-test/route.ts
import { cookies } from 'next/headers'

export async function GET(request: Request) {
  var my_cookie = cookies().get('my_cookie')
  var my_cookie_value = my_cookie ? my_cookie.value : 'DUMMY_DATA'
  return new Response('OK', {status: 200, headers: {'Set-Cookie': `my_cookie=${my_cookie_value}`}})
}

http://~/api/cookie-test を呼び出して Set-Cookie が設定されていれば成功です。

$ curl -i -H "Cookie: my_cookie=AAA" http://localhost:3000/api/cookie-test
HTTP/1.1 200 OK
set-cookie: my_cookie=AAA
  :
クエリーパラメータ(Query parameters)

https://~/~?query=hello のようなURL中のクエリパラメータを得るには NextRequest.nextUrl.searchParams から get() します。

./app/api/query-test/route.ts
import { type NextRequest } from 'next/server'

export function GET(request: NextRequest) {
  const param1 = request.nextUrl.searchParams.get('param1')
  console.log({param1})
  return Response.json({ result: 'OK' })
}

http://~/api/query-test?param1=Hello にアクセスしてサーバ側のコンソールに { param1: 'Hello' } と表示されれば成功です。

フォームデータ(Form data)

<form method="POST"> で送信されるフォームデータを参照するには formData() を await して get() します。

./app/api/form-test/route.ts
export async function POST(request: Request) {
  const formData = await request.formData()
  const name = formData.get('name')
  const email = formData.get('email')
  return Response.json({ name, email })
}

http://~/api/form-test を下記の様に呼び出して下記の様に表示されれば成功です。

$ curl -X POST http://localhost:3000/api/form-test -d 'name=Yamada&email=yamada@example.com'
{"name":"Yamada","email":"yamada@example.com"}
CORS

画面とAPIサーバが異なるオリジンの場合、CORS を設定する必要があります。 ヘッダで Access-Control-Allow-* を返却します。

./app/api/cors-test/route.ts
export async function GET(request: Request) {
  return Response.json({ result: 'OK' }, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': 'https://www.example.com/',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  })
}

その他

クエリーパラメータ(searchParams)

https://~/~?param1=hello のようなURL中のクエリパラメータを得るには searchParams を参照します。

./app/query-test/page.tsx
import { useSearchParams } from 'next/navigation'

export default function Page({params, searchParams}) {
  const param1 = searchParams?.param1
  return (
    <>
      <h1>Query Test</h1>
      <div>{param1}</div>
    </>
  )
}

http://~/query-test?param1=Hello にアクセスして Hello が表示されれば成功です。

リダイレクト(Redirect)

リダイレクトするには redirect() を使用します。

./app/redirect-test/page.tsx
import { redirect } from 'next/navigation'

export default function Page() {
  redirect('https://www.google.com/')
}

http://~/redirect-test にアクセスして Google にリダイレクトされれば成功です。

メタデータ(Metadate)

メタデータとしてタイトルを設定してみます。

./app/layout.tsx
import './global.css'
import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'NEXT.JS',
}

export default function RootLayout({
  :

http://~/ を開いてブラウザのタブに NEXT.JS と表示されていれば成功です。

ミドルウェア(Middleware)

./middleware.ts には、Next.js が page.tsx を呼び出す前に行う共通処理を記述できます。リクエストのロギングや認証チェックなどに利用できます。

./middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  console.log(request.url)
  return NextResponse.redirect(new URL('/', request.url))
}

export const config = {
  matcher: '/middleware-test/:path*',
}

http://~/middleware-test にアクセスして / にリダイレクトすれば成功です。

config.matcher にはミドルウェアを適用するパスを指定します。複数指定や正規表現も使用できます。:path* は任意のパス名を示します。

./middleware.ts
export const config = {
  matcher: ['/mid-test1/:path*', '/(mid-test2|mid-test3)/:path*'],
}

redirect() は別のパスにリダイレクトします。rewrite() はURL表示はそのままで実行内容のみを転送します。

./middleware.ts
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/mid-test1')) {
    return NextResponse.redirect(new URL('/', request.url))
  }
  if (request.nextUrl.pathname.startsWith('/mid-test2')) {
    return NextResponse.rewrite(new URL('/', request.url))
  }
}

http://~/mid-test1 にアクセスするとトップページにリダイレクトします。http://~/mid-test2 にアクセスすると URL はそのままでトップページの内容を表示します。


Copyright (C) 2024 杜甫々
初版:2024年2月4日 最終更新:2024年2月4日
http://www.tohoho-web.com/ex/nextjs.html