【開発】Next.js App Routerでアプリを作る

フォルダ構成
前提
・app配下のlayoutにはグローバルなcontextだけ定義する→layoutのスタイルは設定しない。ただしスマートフォン対応のためbodyには min-height: 100dvh;を設定する。
・ページはuse clientにしない→metadataがexportできないから
・app配下には(default)フォルダを作成してそこをルートとする
コンポジション
app配下はpage毎、feature毎になるので、pageに属するcomponentやassetsはページのルートフォルダ配下に配置する

通信
以下のセキュリティブログを参考に組み立てる
CRUD手段
通信レイヤーを作成する
Next.jsだと2つある。
1. data layer
サーバーから呼ばれることが多い。読み取り系の通信を担当
2. Server Actions (SA)
書き込むようなものSAを使用する
SAはAPIのエンドポイントのようなものなのでエンドポイントさえ知っていれば誰でも叩けるので注意。必ず認証やバリデーションを行う。認証の場所は後述する。

デザイン
tailwindcssを使用する
レスポンシブデザイン
className="lg:xxx"のようにすることでlgサイズのスタイルを指定できる。
ダークモード
公式docにあるのでそちらを参照。
注意すべきは色の指定方法で、何でもかんでも色を付けるとダークモードが無視される。
例えば文字や背景の色を少し暗くするぐらいであれば、mutedやmuted-foregroundを使用する
テキストであればtext-muted-foregroundとすることで薄い文字にできる。

OGイメージ、Xイメージの設定
favicon

メタデータ
ルートレイアウトで以下を設定
export const metadata: Metadata = {
title: {
template: '%s | Test',
default: 'Test',
},
description: "Shun's Blog",
}
export default function RootLayout({
page.tsxのmetadataのtitle部分が%sと対応する
export const metadata: Metadata = {
title: 'hoge',
description: "hogeのページです",
}
export default function Home() {
Homeへ遷移した場合、タブには「hoge | Test」と表示される

静的ページのビルド
next.config.mjsに以下を設定する
const nextConfig = {
output: "export"
}

エラーハンドリング
fetchのcatchパターン
fetch関数の結果がcatchに入るパターンについて、HTTPステータスコードに当てはまるかどうかを説明します。
- ネットワークエラー:
ネットワーク接続が失敗した場合(例:サーバーがダウンしている、インターネット接続がないなど)。
HTTPステータスコード: 該当なし。ネットワークエラーはHTTPリクエストがサーバーに到達しないため、ステータスコードは存在しません。 - CORSエラー:
クロスオリジンリソース共有(CORS)ポリシーに違反している場合。
HTTPステータスコード: 該当なし。CORSエラーはブラウザによってブロックされるため、ステータスコードは返されません。 - リクエストのタイムアウト:
リクエストがタイムアウトした場合(ただし、fetch自体にはタイムアウト機能がないため、AbortControllerを使用して手動でタイムアウトを設定する必要があります)。
HTTPステータスコード: 該当なし。タイムアウトはクライアント側の設定であり、サーバーからのレスポンスがないためステータスコードは存在しません。
DNS解決エラー:
ドメイン名が解決できない場合。
HTTPステータスコード: 該当なし。DNS解決エラーはHTTPリクエストがサーバーに到達しないため、ステータスコードは存在しません。
これらのエラーはすべてfetch関数のcatchブロックに入りますが、HTTPステータスコードは返されません。
response.ok
fetch関数のレスポンスオブジェクトにはokプロパティがあり、これはレスポンスが正常(ステータスコードが200-299の範囲)であるかどうかを示します。response.okがtrueの場合、レスポンスは正常です。falseの場合、エラーが発生したことを示します。
fetch('https://example.com/data')
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Network response was not ok');
}
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Fetch error:', error);
});
axiosの場合
axiosを使用した場合、HTTPステータスコードが400以上(例えば、500エラー)の場合でもcatchブロックに入ります。これはaxiosのデフォルトの動作です。

フォーム検証の仕組み
3回の検証が行われる
- フロントでreact-hook-formを使用して検証
- サーバーサイドでzodの検証
- supabaseならDB側でRLSによる検証
フロントの検証は改ざん可能なため、zodの検証は必須。検証が漏れてもDBでも拾えるようにしておくことでより堅牢なアプリとなる。

tailwindcss お作法

Next.js View Transitions

v14 セットアップ

フォルダ構成
◆ 関連処理は1ファイルにまとめて書く
◆ 共通処理は /components 等にフラット設置
◆ ファイル名はケバブ
◆ 状態管理ライブラリやテストが見当たらない
例
共通パーツは /components にフラットに置かれがち。これは賛否あるかもだけど個人的には「これどのドメイン(機能)にしよっか..)と悩んだり仕様変更時のお引越ししたりが面倒なのでフラット設置はあり。
ファイル名ケバブについては Next.js がそもそも not-found.tsx などケバブベースなのと、大文字小文字のリネームで git がバグる問題もあり個人的にもケバブの方が Next.js に馴染む気がする。
Next.js 公式 Learn でもケバブでガイドされている。
状態管理ツール不在については
◆ App Router のキャッシュによりDBソースをグローバルステート感覚で使える
◆ UI周りの状態は shadcn/ui 等に委ねる
◆ RSC からは swr 等でステート管理
◆ 余計にコンポーネント分割せずベタ書きすることで props リレーを軽減
という背景のもと不要説。
テストについてはユニットテストが async コンポーネント非対応なのでサンプルリポで混ぜようがない状況。公式Doc曰く一旦 e2e で頑張れとのこと。
にしても shadcn や Vercel メンバーのProd リポでもテストの気配がないのは一体...?🤔
ちなみに test や story をパッケージする場合、Bパターンが一般的だった。元 Atlassian エンジニアも index.tsx 量産するとファイル名で現場直行がむずくなるから命名をおすすめしてる。

favicon
safariはsvgのfaviconに対応していないので注意
以下で生成できる
android-chrome:アンドロイド関係
apple-taouch-iconはapple-icon.pngにして、他はicon1.pngにしてappフォルダ配下に配置する
OG image
OG image確認(Next.jsの開発ツールでも確認できる)
PORT 3000のアプリをwebに公開するコマンド
npx ngrok http 3000

アニメーション
tailwindcssでアニメーションのプロパティを動的に変化させる方法
以下のようにアンダーバーで繋げることでスピードを変えられる
animate-[slide-in-bck-top_0.7s]

キャッシュ、動的、静的
ƒ:動的レンダリング
○:静的レンダリング
静的ページ
全てのリクエストに対して同じレンダリング結果を返却する
静的fetch
cacheオプションがないので、buildしたとき以外はfetchが再度実行されることはない。
fetch("https://")
動的ページ
リクエストの内容に応じて異なるレンダリング結果を返却する
3つの要因がある
1. 動的fetch
no-storeにすると毎回fetchされるので静的ページではなくなる
fetch("https://", {cache: "no-store"})
2. 動的関数
cookies()
// クエリパラメータのこと
searchParams
3. Dynamic Segment
[xxx]のページは動的レンダリングとなる。

流体フォント

文字列の折り返しがいい感じにされるようにする
className="text-balance"

複数回middlewareが呼ばれることを回避する
prefetchはSWRで行っているっポイ

middlewareを理解する

URL操作
new URLSearchParams
const searchParams = new URLSearchParams({
page: '1',
per_page: '10',
})
console.log(`api/v/${searchParams}`) // => "api/v/page=1&per_page=10"

Util
lodashの改善版
- 正しい挙動をするものを自作しなくてよい
- パフォーマンスが高い