フロントエンド開発の先端を突っ走るNext.js
next.js アプリの初期化( npx create-next-app@latest <アプリ名> ) または ( yarn 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 レイアウト(再レンダリングされない
page.tsx ルーティングファイル(一時的にルートをオフにする場合は ___page.tsx にリネームするなどします)
loading.tsx ローディング コンポーネント
not-found.tsx NotFound コンポーネント
error.tsx エラー コンポーネント
global-error.tsx グローバルエラー コンポーネント
route.tsx サーバー側 API エンドポイント
template.tsx レイアウト(再レンダリングされる)
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 でリダイレクトする

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

import { redirect } from "next/navigation"

redirect("/mypage")

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

import { notFound } from "next/navigation"

return notFound()

● 独自の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でサイト全体のフォントを設定する

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() {}

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

app/posts/[id]/page.tsx

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

● 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>
  )
}
No.2407
01/30 21:08

edit