---- | App Router | Pages Router |
---|---|---|
APIルートの名前 | Route Handlers | API Routes |
useRouterの場所 | import {useRouter} from "next/navigation" | import { useRouter } from "next/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 }
);
};
import { redirect } from "next/navigation"
redirect("/mypage")
import { notFound } from "next/navigation"
return notFound()
または src/middleware.ts でリダイレクトすることができます。
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push("/mypage")
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
画面遷移時に各ページ実行前に middleware で処理を挟み込めます。
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が動作する対象のパス
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' }
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>
)
https://nextjs.org/docs/app/building-your-application/optimizing/metadata
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
app/posts/[id]/page.tsx
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
return {
title: `投稿データ{params.id}`,
}
}
app/posts/[id]/page.tsx
// 検索エンジンにインデックスさせない設定
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
// no-cache
nocache: true,
},
}
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に設定しないとエラーとなります。)
app/globals.css (ファイル名は任意。デフォルトでは globals.css)
body {
padding: 60px;
}
app/layout.tsx(ファイル名は任意。デフォルトでは layout.tsx)
import './global.css'
app/hello/page.module.css (ファイル名は任意)
.page_container {
display: grid;
gap: 10px;
}
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;
}
https://blog.furu07yu.com/entry/using-route-groups-for-layouts-in-nextjs-13
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
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
// cacheオプション指定なし
const data = await fetch(url);
// 'no-cache'を指定してもビルド時のfetchは実行されます
const data = await fetch(url, { cache: 'no-cache' });
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' });
.....
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)