Next.js App routerでパフォーマンス・UX改善をする4つの方法
はじめに
Next.js App routerは、なんとなく開発しているとUXの悪いアプリケーションができてしまいがちです。
ですが、今回紹介する方法を実装すれば、ユーザーがストレスの感じないアプリケーションをそれなりに開発できますので、ぜひみていただければと思います。
対象: Next.js v14
この記事を見て欲しい人
- Next.js App router 初心者の人
- Next.js App routerで開発したアプリケーションが、もったりしていて悩んでいる人
- Next.js App routerで、UXやパフォーマンスの良いアプリケーションを作りたい人
prefetchでページ遷移の速度を上げる
Next.jsのLinkコンポーネントとuse Routerには、prefetchという機能があります。
prefetchを使うと、ページにアクセスする前に、次のページを読み込んでくれるようになります。
ユーザー体験で言うと、クリックした瞬間に次のページが表示されて、SPAのアプリケーションのような早いページ遷移が実現できます。
サンプルコード
// Linkの場合
<Link prefetch href="/contact">
Contact
</Link>
// routerの場合
const router = useRouter()
router.prefetch('/contact')
参考
SuspenseでLoading UIを表示させる
app routerでは、非同期コンポーネントでデータフェッチをするスタイルがスタンダードです。
ですがこのままでは、非同期コンポーネントが読み込み中に、ページに何も表示されない状態ですので、ユーザー的には結構しんどいです。
このような時は、Suspenseを使って、読み込み中のUIを表示させましょう。
Suspenseは、非同期コンポーネントが完了するまでの間、別のUIを表示させることができるものです。
Suspenseでコンポーネントをラッピングして使います。
サンプルコード
export default async function ProfilePage() {
return (
// ProfileContainerが完了するまで、LoadingUIを表示させる。
<Suspense fallback={<LoadingUI />}>
<ProfileContainer />
</Suspense>
);
}
async function ProfileContainer() {
const user = await getCurrentUser();
if (!user) redirect('/login');
const userData = {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
email: user.email,
role: user.role,
};
return (
<ProfilePresentation
user={userForPresentation}
/>
);
}
参考
非同期コンポーネントを分割して、並行データフェッチをする
前述したように、サーバー側で非同期コンポーネントを実装してデータフェッチするのがApp routerのスタンダードな方法なのですが、1つの非同期コンポーネントに複数のデータフェッチを実装すると、直列で1つずつ非同期処理が動きます。
そうなると、読み込み速度に影響が出ます。
そこで、1つの非同期コンポーネントにまとめずに、処理を分割できる場合は、非同期コンポーネントを分割しましょう。
例えば、ブログ記事一覧画面があったとします。
この画面では、記事の一覧とカテゴリの一覧が表示されていると仮定します。
この場合に、記事一覧データをフェッチするコンポーネントと、カテゴリ一覧データをフェッチするコンポーネントを分けると、Next.jsは、並行処理をしてくれます。
Promise.all的なことをしてくれるイメージですね。
前の処理を待たずに、記事一覧とカテゴリ一覧が並行して処理されるので、読み込み速度が速くなります。
サンプルコード
export default function BlogListPage({
searchParams,
}: {
searchParams: { page?: string };
}) {
return (
<>
<h1 className="mb-2 px-4 text-xl font-bold">ブログー一覧</h1>
<BlogListContainer searchParams={searchParams} />
</>
);
}
function BlogListContainer({
searchParams,
}: {
searchParams: { page?: string };
}) {
const page = Number(searchParams.page);
const currentPage = isNaN(page) ? 1 : page;
return (
<div>
<Suspense fallback={<LoadingUI />}>
<BlogList page={page} />
</Suspense>
<Suspense fallback={<LoadingUI />}>
<CategoryList />
</Suspense>
</div>
);
}
async function BlogList({page}: {
page: number
}) {
const blogList = await getBlogList({page});
return (
<BlogListUI blogList={blogList} />
)
}
async function CategoryList() {
const categoryList = await getCategoryList();
return (
<CategoryListUI categoryList={categoryList} />
)
}
revalidateを利用して、最新のデータをスムーズに取得する
サーバーアクションなどで、データの更新をした後、表示しているデータを最新化する必要があります。
この時には、revalidateを使ってデータの再検証を行うことで、データ更新後にスムーズに新しいデータを表示できます。
revalidatePathとrevalidateTagの2つがありますが、revalidatePathの方がシンプルなので、こちらから使ってみるのがおすすめです。
revalidatePathは、引数で指定したパスを再検証する関数です。
サンプルコード
'use server'
import { createClient } from '@supabase/supabase-js'
import { revalidatePath } from 'next/cache'
import { Database } from '@/types/database.types'
// Supabaseクライアントの初期化
const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export async function updateMember(formData: FormData) {
try {
// フォームデータの取得と検証
const id = formData.get('id')
const firstName = formData.get('firstName')
const lastName = formData.get('lastName')
const email = formData.get('email')
if (!id || !firstName || !lastName || !email) {
return {
success: false,
message: '必須項目が入力されていません'
}
}
// メンバー情報の更新
const { error } = await supabase
.from('members')
.update({
first_name: firstName.toString(),
last_name: lastName.toString(),
email: email.toString(),
updated_at: new Date().toISOString()
})
.eq('id', id.toString())
if (error) {
throw new Error('メンバー情報の更新に失敗しました')
}
// キャッシュの再検証
// 再検証して、新しいプロフィールデータが取得できる
revalidatePath('/profile')
return {
success: true,
message: '更新成功'
}
} catch (error) {
return {
success: false,
message: '更新失敗'
}
}
}
参考
終わりに
以上です。
Next.jsは、色々な機能がある分、なんとなく使っていると微妙な質のアプリケーションを爆誕させてしまうので、しっかりキャッチアップして、理解して実装できるとGoodですね。
私も今年本格的に使うようになった人間なので、他にも良いtipsがあればご共有いただけますと助かります。
Discussion