フロントエンド開発の先端を突っ走るNext.js
next.js アプリの初期化( npx create-next-app@latest <アプリ名> ) または yarn create next-app <アプリ名> または bun create next-app <アプリ名> )

Next.js の App Router ざっくりまとめ

● Next.jsの App Router , Pages Router で登場する名称

---- App Router Pages Router
APIルートの名前 Route Handlers API Routes
useRouterの場所 import {useRouter} from "next/navigation" import { useRouter } from "next/router"

● app router の特別な命名

/app/ ディレクトリ以下では次の命名は特別な意味を持ちます。

layout.tsx レイアウト(再レンダリングされない
template.tsx レイアウト(再レンダリングされるので意図的に際レンダリングしたい場合はこっちを使う
page.tsx ルーティングファイル(一時的にルートをオフにする場合は ___page.tsx にリネームするなどします)
loading.tsx ローディング コンポーネント
not-found.tsx NotFound コンポーネント
error.tsx React Error Boundary を利用したエラー コンポーネント。
next.js ではデフォルトで以下のようにErrorBoundaryが設定されているので error.tsx を記述するだけでpage.tsxのエラー補足をすることができます。 なので、layout.tsx や template.tsx で発生したエラーの捕捉は error.tsx ではできません
global-error.tsx アプリ全体のエラーを捕捉する。page.tsxと同階層に error.tsx が存在しない場合は global-error.tsx が表示されます
Handling Errors in Root Layouts
layout.tsx や template.tsx で発生したエラーの捕捉は global-error.tsx でしかできないようです。
global-error.tsx ではコンテキストが受け取れないので注意(error.tsxでやりましょう。)
route.tsx サーバー側 API エンドポイント
default.tsx 並列ルートのフォールバック コンポーネント

src/app/api/hello/route.ts

import { NextResponse, NextRequest } from 'next/server';

export const GET = async () => {
  return NextResponse.json(
    { message: 'Hello, Next.js route.ts!' },
    { status: 200 }
  );
};

export const POST = async (request: NextRequest) => {
  const body = await request.json();
  console.log({ body });

  // Do something

  return NextResponse.json(
    { message: 'Operation successful' },
    { status: 200 }
  );
};

● next.js app router でリダイレクトする

1. サーバーサイドの場合 ( redirect )

・ /mypage へリダイレクトする

import { redirect } from "next/navigation"

redirect("/mypage")

・ 404画面へリダイレクトする

import { notFound } from "next/navigation"

return notFound()

または src/middleware.ts でリダイレクトすることができます。

2. クライアントサイドの場合 ( useRouter )

・ /mypage へリダイレクトする

import { useRouter } from 'next/navigation'

const router = useRouter()
router.push("/mypage")

● 独自の404Not Foundページを作成する

app/not-found.tsx

import Link from 'next/link'
 
export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href="/">Return Home</Link>
    </div>
  )
}

File Conventions: not-found.js | Next.js

● app router の Middleware

画面遷移時に各ページ実行前に middleware で処理を挟み込めます。

(例1: /dashboard へのアクセスを /dashboard-new へリダイレクトさせる)

src/middleware.ts (注意: src/app/middleware.ts ではありません)

import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL("/dashboard-new", request.url))
}

export const config = {
  matcher: "/dashboard",
}
matcher: middlewareが動作する対象のパス

(例2: /dashboard へアクセスしたとき、ログイン前の場合は /login へリダイレクトさせる)

src/middleware.ts (注意: src/app/middleware.ts ではありません)

import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import { currentUser } from "@/firebase/firebaseAuth"

export async function middleware(request: NextRequest) {
  return (await isAuthenticated())
    ? NextResponse.next()
    : NextResponse.redirect(new URL("/login", request.url))
}

export const config = {
  matcher: "/dashboard",
}

async function isAuthenticated(): Promise<boolean> {
  // サーバーサイドでユーザー認証を確認するロジック
}

middlewareはサーバーサイドなので、例えば firebaseのようにユーザーの取得がクライアントサイドの場合は ここに記述せずにクライアント側で記述するか、サーバーでも認証状態を持つ必要があります。

ローカライゼーション

https://zenn.dev/cybozu_frontend/articles/nextjs-i18n-app-router

● ルーティング ( 動的ルーティング )

app/blog/[slug]/page.tsx

この時 /blog/hoge/?page=2 にアクセスした時 hoge や pageを取得したい時は以下のように取得します。

type PageProps = {
  params: {
    slug: string
  }
  searchParams: { [key: string]: string | string[] | undefined }
}

export default function Page({ 
    params ,
    searchParams,
}: PageProps) {
  return <div>My Post: {params.slug}</div>
}

取得した値は以下のようになります。

params = { slug: 'acme' }
searchParams ={ page: '2' }

● app router の layout.tsxでサイト全体のフォントを設定する

src/app/layout.tsx

const notoSansJp = Noto_Sans_JP({
  weight: ["500"],
  subsets: ["latin"],
  variable: "--font-noto-sans-jp",
})
  return (
    <html lang="en">
      <body className={notoSansJp.className}>{children}</body>
    </html>
  )

● app routerでページごとのHTMLタイトルなどヘッダの設定方法

https://nextjs.org/docs/app/building-your-application/optimizing/metadata

1. layout.tsx または page.tsx に静的に設定する

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}

2. page.tsx にページ毎に動的に設定する

app/posts/[id]/page.tsx

export async function generateMetadata({
  params,
}: PageProps): Promise<Metadata> {
  return {
    title: `投稿データ{params.id}`,
  }
}

3. page.tsx にページ毎に検索エンジン(Google)のインデックスを設定する

app/posts/[id]/page.tsx

// 検索エンジンにインデックスさせない設定

export const metadata: Metadata = {
  robots: {
    index: false,
    follow: false,
    // no-cache
    nocache: true,
  },
}

● page.tsx で httpヘッダ , cookie , クエリパラメーターを取得する

httpヘッダ , cookie 取得

import { cookies, headers } from 'next/headers';

  // cookie
  const cookieStore = cookies();
  console.log('● cookieStore');
  console.log(cookieStore);

  // headers
  const httpHeaders = headers();
  console.log('● httpHeaders');
  console.log(httpHeaders);

クエリパラメーター 取得

import MyServerComponent from "./MyServerComponent";

type PostsPageSearchParams = {
  page?: string;
  sort_by?: string;
  sort_order?: "asc" | "desc";
};
type Props = {
  params: {};
  searchParams: PostsPageSearchParams;
};

export default function Page(props: Props) {
  const searchParams = props.searchParams;
  console.log(searchParams.pages); // ❌ Error: Property 'pages' does not exist on type 'PostSearchParams'. Did you mean 'page'?
  return <MyServerComponent searchParams={searchParams}></MyServerComponent>;
}

● 画像

こちらがよくまとまっています。
https://dev.classmethod.jp/articles/next-js-image-component/

● css ( グローバルcss )

( グローバルcss 1. ) cssを以下のファイルに保存する

(タグのcssを設定する場合はグローバルcssに設定しないとエラーとなります。)

app/globals.css (ファイル名は任意。デフォルトでは globals.css)

body {
  padding: 60px;
}

( グローバルcss 2. ) レイアウトコンポーネントから参照する

app/layout.tsx(ファイル名は任意。デフォルトでは layout.tsx)

import './global.css'

● css ( cssモジュール )

( cssモジュール 1. ) cssを以下のファイルに保存する

app/hello/page.module.css (ファイル名は任意)

.page_container {
    display: grid;
    gap: 10px;
}

( cssモジュール 2. ) コンポーネントから参照する

app/hello/page.tsx(ファイル名は任意)

import css from "./layout.module.css";

jsx の classNameに指定します

<div className={css.page_container}>
   hoge
</div>
  • 注意点 ケバブケース ( kebab-case ) は使用できません。
    キャメルケースを使用しましょう。

  • 注意点 タグ名は直接使用できません

      /* こちらは反映されません */
      .page-container div {
        background-color: red;
      }
    

● App routerのRoute Groups

https://blog.furu07yu.com/entry/using-route-groups-for-layouts-in-nextjs-13

● Next.js App router で Tanstack Queryを使用する

src/app/providers.tsx

import React, { ReactNode, useState } from "react"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"

type ProvidersProps = {
  children: ReactNode
}

const Providers: React.FC<ProvidersProps> = ({ children }) => {
  const [queryClient] = useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
}

export default Providers

src/app/layout.tsx

// app/layout.jsx
import Providers from './providers'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

● サーバーサイドでのエラー補足

https://x.com/azu_re/status/1760494278965629256?s=20

● 開発モードで fetch のログを表示する

https://nextjs.org/docs/app/api-reference/next-config-js/logging

next.config.js

module.exports = {
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

参考 : https://speakerdeck.com/mugi_uno/next-dot-js-app-router-deno-mpa-hurontoendoshua-xin

● ビルド時にサーバーサイドfetchが自動で実行される(Next.js の fetchCacheオプション)

・ビルド時に fetch が実行される例
// cacheオプション指定なし
const data = await fetch(url);
// 'no-cache'を指定してもビルド時のfetchは実行されます
const data = await fetch(url, { cache: 'no-cache' });
・ビルド時に fetch が実行さない例
const data = await fetch(url, { cache: 'no-store' });

または page.tsx や layout.tsx の先頭に

export const dynamic = 'force-dynamic';

と記述すると、ダイナミックページであることが強制されるのでビルド時のfetchも走りません。

こちらもビルド時のfetchは走りません。

page.tsx

export const dynamic = 'force-dynamic';

const FooPage = async () => {
  // 'force-cache' となっているが、ビルド時のfetchは走らない
  const data = await fetch(url, { cache: 'force-cache' });
  .....

● jestを設定する

https://nextjs.org/docs/app/building-your-application/testing/jest

import type { Config } from 'jest'
import nextJest from 'next/jest.js'
 
const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})
 
// Add any custom config to be passed to Jest
const config: Config = {
  coverageProvider: 'v8',
  testEnvironment: 'jsdom',
  // Add more setup options before each test is run
  // setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}
 
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config)
添付ファイル1
No.2407
10/11 17:56

edit

添付ファイル