Top

Commentary

Page Transitions

ページ遷移

はじめに #

ウェブで一番多いのはリンクをクリックして、別のページに遷移する操作です。ページ遷移は一番基本のUI/UXであり、ユーザの体感に非常に大きな影響を与える大事なものです

また開発者としては、一番よく作るインタラクションですので、開発効率を高める上でもコードが簡単に書けることが非常に重要です。セキュリティもしっかり確保する必要があります。

ここでは各種の技術を紹介しながら、そのUI/UXの比較およびコードの書き味の比較をします。

ページ遷移技術の比較
技術データロードローダー表示prefetchセキュリティその他
ブラウザネイティブ(MPA)先にロードするしないJavaScript, CSSは要再読み込み
Hotwire (Turbo Drive)先にロードするするhoverでprefetch
Next SSG先にロード要作成する△ (要DAL)動的なサイトでは使えないが、参考までに紹介
Next useEffect後にロード要作成静的な部分まで△ (要DAL)useEffect内のfetchはprefetchされない
Next page router SSR先にロード要作成しない△ (要DAL)SSRを使うとprefetchしない
Next app router Server component先にロード要作成静的な部分まで△ (要DAL)dynamic componentを使った場合はloading.jsのところまでprefetch

ブラウザネイティブ(MPA)によるページ遷移 #

まずは一番基本的なブラウザネイティブなMPAによるページ遷移を確認します。MPAによる画面遷移のデモをご確認ください。またコードは/usersのページと/productsをご覧ください。

一番下のボタン(Products/Usersへ遷移)で/users/productsの間を画面遷移しますが、これがブラウザネイティブなMPAの画面推移です。

コード #

ごく普通のHTMLページです。通常通りにaタグを使ったリンクを使用していて、非常に簡単です。

<a href="/api/hotwire/products"
   class="btn-primary">Productsへ推移 (Turbo Off)</a>

UI/UX #

  • 遅延(Delay)が少ない場合は特にストレスのないUI/UXです
  • 遅延(Delay)を1000msや2000msに設定すると、画面が切り替えるのを待たされます。しかしその間は画面の上部でプログレスバーが表示されますので、ブラウザが反応し、リクエストが正しく送信されているというフィードバックはあります

まとめ #

  • 全体として特に不満のないUI/UXです

いまだにMPA的なページ推移がウェブで大多数です。例えばAmazon楽天はMPA的な画面遷移をします。Appleもそうですし、ソフトバンクもMPAです。

Hotwire Turbo Driveによるページ遷移 #

次にHotwire Turbo Driveによるページ遷移を確認します。Turbo Driveのデモはここから確認できます。またTurbo Driveのコードは先のMPAの場合とほとんど変わりませんがGitHubで確認できます。

コード #

  • 上のMPAの例との唯一の違いはTurbo DriveのJavaScriptファイルの読み込みです。Turbo Driveのコードの最上部ではlayouts/header.ejsファイルを読み込んでいますが、ここの中の<script src="/hotwire/javascript/turbo.es2017-esm.js" ...>でTurbo Driveを読み込んでいます。turbo.es2017-esm.jsのファイルは公式ドキュメントの通りにコンパイル済みのJavaScriptファイルをダウンロードしたののです。
  • それ以外はMPAの場合と全く変わりませんので、引き続き非常に簡単なものになっています。リンクも引き続き普通のaタグです。

templates/page_drive/users.ejs

<%- include("../layouts/header.ejs") -%>

templates/layouts/header.ejs

<!doctype html>
<html lang="ja">
<head>
  ...
  <script src="/hotwire/javascript/turbo.es2017-esm.js" data-turbo-track="reload" type="module"></script>
  ...
</head>
...

UI/UX #

  • MPAの場合と比べて、画面の遷移が圧倒的に速くなります。遅延(Delay)が300msの場合はほぼ瞬間的に画面が遷移します。1000msや2000msの場合でも大幅に速くなっています。
  • 遅延が1000msや2000msの場合は画面の最上部に青いプログレスバーが表示されることがあります。表示に時間がかかる場合はフィードバックが表示され、ブラウザが正しく動作していること、ネットワーク通信中であることをユーザに伝えています。

まとめ #

  • コードはほとんどそのままであるにもかかわらず、UI/UXが大幅に改善されています。これはSPAの効果も多少ありますが、圧倒的にPrefetchの効果およびキャッシュの効果です
  • Turboをインストールするだけでこれだけ効果がありますので、ほとんどのケースではTurbo Driveを使えば良いと思います。たまに悪影響が出ることもありますが、その場合はTurbo Driveをオフにすることも可能です。

Next.js SSGによるページ遷移 #

Next.js側はまず最初にSSGの例を紹介します。Next.jsのSSGはページをビルド時に作成するものです。本サイトは主に頻繁にデータが更新されるウェブアプリを想定していて、本来であればSSGはこれにマッチしないのですが、Next.js SSRとの比較のために紹介しています。

コード #

コードは/users、および/productsで紹介しています。

getStaticProps()の中で直接allUsers()でUser情報を取得するものになっています。

export async function getStaticProps() {
  const users = await allUsers()
  return {props: {users}}
}

UI/UX #

  • 体感としてはTurbo Driveを使用した場合とほとんど同じになっています。つまり画面の遷移は非常に速く、ほぼ瞬間的に画面が遷移することもあります
  • 一般的にはSSGはCDNなどを使ってキャッシュされるため、ネットワーク遅延やサーバ遅延はわずかになります。今回は遅延を0msに固定していますので、なおさら速くなっています

まとめ #

  • UI/UXはHotwire Turbo Drive並みであり、MPAと比べて大幅に改善されています
  • しかしSSGはあくまでもビルド時にgetStaticProps()によってページを作成しますので、データが頻繁に更新されるタイプのウェブサイトでは使用できません。今回は比較のために例示しましたが、本サイトの他のページと同じ条件ではありません
  • Hotwire Turbo Driveの場合と同様に、高速化はSPAの効果も多少ありますが、圧倒的にPrefetchが効いています。Next.jsはSSGの場合はprefetchが効きます。

Next.js useEffectによるページ遷移 #

Next.jsのSSRを使わない場合は、useEffect()などを使ってページに表示するデータを読み込みます。純粋なSPAを作るときによく採用されている手法ですが、Next.jsの場合もしばしば使われます。

useEffect()を使ったデモはここからご確認いただけます。またコードはUsers側および Products側をGitHubに公開しています。

コード #

下記のようにuseEffect()の中でfetch()を使い、データを取得しています。そのデータはsetUsers()でステートに詰め込み、画面を表示させています。また途中のローディング画面を表示するためにsetLoading()でロード中表示のステートもセットしています。

なお下記のコードはエラー処理が含まれていません。エラーを正しく処理し、ソフト404ページを表示したり、ユーザにフィードバックするのであればコードはさらに複雑になります。

useEffect(() => {
  console.log("Fetch start for Users useEffect")
  fetch("/api/users").then(res => res.json())
    .then(data => {
      setUsers(data)
      setLoading(false)
    })
},[])

UI/UX #

  • 画面遷移のリンク(ボタン)を押すとすぐに画面が切り替わり、ローディング中のアニメーションが表示されます。フィードバックは速く、適切です
  • ローディングアニメーションの表示は速いものの、実際にコンテンツのある新しいページが表示されるまでの時間はMPAの場合と変わりません

まとめ #

  • MPAやTurbo Driveの場合と比べてコードは複雑になります
  • ローディングアニメーションはMPAの場合よりは目立ちますが、フィードバックの有無という意味ではMPAの場合と変わりません
  • 実際に新しいページが表示されるまでにかかる時間はMPAと変わらず、Turbo Driveのような明確なメリットはありません

Next.js Page router SSRによるページ遷移 #

Page routerのSSRは初回アクセス時のページローディングが速いことや、SEOに強いなどのメリットがあります。これはいずれも初回ロード時の話のみです。一旦ユーザがウェブサイトを訪問した後は、2回目以降のページ遷移がUI/UX上重要になります。Page router SSRのデモサイトはこちらにあります。

コード #

Page router SSRのコードはUsers側Products側をご覧ください。

なお、下記のコードのhideLoadingIndicatorのところは、ローディングアニメーションに関わるところで、デモでのみ使用するものです。通常の実装では不要です。

それ以外のところは比較的シンプルで、単にpageコンポーネントに渡すusersを用意しています。エラー処理を含めるともう少し複雑になります。

pages/users_ssr/index.tsx

// Simulate Next.js acting as a BFF for a JSON API server
export async function getServerSideProps(context: GetServerSidePropsContext) {
  console.log("Fetch start for Users SSR")
  const res = await fetch(process.env.URL + "/api/users")
  const users = await res.json()
  const hideLoadingIndicator = !!(context.query.hide_loading_indicator);
  return {props: {users, hideLoadingIndicator}}
}

UI/UX #

  • 画面遷移のリンク(ボタン)を押しても、フィードバックがありません。遅延が300ms程度であれば問題ありませんが、遅延が2000msの場合はブラウザが正常に動いているかどうか、サーバに正しくリクエストが送られたかどうかが心配になります
  • 新しいページが表示されるまでの時間はMPAの場合と変わりません
  • プログレスバーやローディングアニメーションがない分、ユーザは操作に対するフィードバックが得られず、MPAよりもUI/UXは劣っています

まとめ #

  • Next.js/Reactを使っているにもかかわらず、プログレスバーやローディングアニメーションがない分、MPAよりもUI/UXは劣っているのは重大な問題だと思います
  • これについてはLoading Animiationsで詳しく解説しています
  • コードはシンプルです

Next.js App router Server Componentによるページ遷移 #

Server ComponentはSSRとよく似ていますが、Suspenseを使ってローディングアニメーションを簡単に表示できるという特徴があります。このおかげでフィードバックがないという上記のSSRの問題点を解決できますデモはこちらに用意しています。

コード #

Users側Product側のコードをご覧ください。

コードは比較的簡単で、getUsers()関数でusersを取得し、コンポーネントの中で表示しています。なお、timeを取得しているのはNext.js v14のキャッシュの挙動を確認するためで、デモだけのために用意しています。

またloading.tsxのファイルはローディングアニメーションを表示するものです。

app/users_app/page.tsx

// Simulate Next.js acting as a BFF for a JSON API server
async function getUsers(): Promise<User[]> {
  const res = await fetch(process.env.URL + "/api/users")
  const users = await res.json()
  return users
}

export default async function UsersAppIndex() {
  const users = await getUsers()
  const time = new Date().toLocaleTimeString()
  ...

app/users_app/loading.tsx

export default function Loading() {
  // You can add any UI inside Loading, including a Skeleton.
  return <>
    <div className="w-full mt-24">
      <Image src={rocketImage} alt="loader" className="w-16 h-16 mx-auto"/>
    </div>
  </>
}

UI/UX #

  • 画面遷移のリンク(ボタン)を押すと、すぐにローディングアニメーションが表示されます。useEffect()の場合とUI/UXは非常に似ています
  • 新しいページが表示されるまでの時間はMPAの場合と変わりません

まとめ #

  • 画面遷移のリンク(ボタン)を押すと、すぐにローディングアニメーションが表示されますので、SSRの問題点を解決できています。しかしUI/UX的にはuseEffect()と同じになっただけです
  • 新しいページが表示されるまでの時間はMPAの場合と変わらないので、特にメリットはありません
  • SSGの場合はprefetchが効いたため、Turbo Driveのようにページ遷移が高速化されました。しかしNext.jsは動的なページの場合(dynamic rendering)はprefetchが効きません。新しいページが表示されるまでの時間が短縮されないのはこのためです。

各種テクニック(SPA, prefetch, キャッシュ)の効果 #

「たかがページ遷移、されどページ遷移」 #

上述のように、Hotwire Turbo DriveからNext.js Server Componentsに至るまで、ページ遷移のUI/UXを改善するための技術は多数使われています。SPA, prefetch, キャッシュ, suspenseなどがこれに該当します。

しかしすべてが高い効果を発揮するわけではなく、ケースバイケースでもあります。

SPAは基盤技術としての効果 #

上記の表に挙げた各ページ遷移法は、ネイティブ(MPA)を除いて、すべてSPA (Single-page Application: シングルページアプリケーション)です。

ここでいうSPAは、ページ切り替え時にAJAX等を使っていて、一見するとページ全体は切り替わっているものの、裏でロードされたJavaScriptやCSSはそのまま残しているという意味です。前のページをメモリに残しつつ画面遷移するため、よりスムーズなページ切り替えが可能になります。

ただし最近のデバイスではJavaScriptやCSSの読み込みが高速であり、上記のメリットをほとんど感じることができません。実際AstroなどのフレームワークはSSRをするものの、2ページ目のSPA的遷移は省略していて、単純なMPAとして動作します。

このように、SPAというだけではページ遷移のUI/UXの改善は期待できなくなっています。ただしSPAはメモリを維持してくれるため、Prefetchやキャッシュを可能にする基盤技術として重要です。

Prefetchの効果 #

Turbo Driveをインストールするだけで、ページ遷移はヌルサクになります。ネイティブな画面遷移とTurbo Driveによる画面遷移を比べていただくと一目瞭然です。

この効果のほとんどはprefetchによるものです。マウスカーソルがリンクの上をホバーした時に、フライングをしてサーバにリクエストを飛ばします。そして実際にユーザがリンクをクリックしたとき、すでにリンク先ページは読み込まれていますので、瞬間的に画面遷移ができます。

Prefetchの効果
Prefetch effect

Next.jsのprefetchは多くの条件では動かない

Next.jsにもprefetchがあります。しかし多くのケースでは効果がありません。Pages routerの場合、SSRのページではprefetchが効きません。またApp routerでDynamic renderingの場合も最初のloading.jsファイルまでしかprefetchしませんので、prefetchはローディング画面の表示を早めてくれるだけの効果しかありません。逆に言うと、Next.jsの場合、Pages routerのSSGやApp routerのStatic renderingの場合に限ってならprefetchが有効になります

本サイトの例を見ても、Next SSGの場合はヌルサクな画面遷移をします。しかしNext useEffectNext app routerの場合はまずはローダー画面だけがすぐに表示されるものの、データのある画面が現れるまでは待たされます。そしてNext SSRの場合はクリック直後はページがローダーも現れず、しばらく経ってからデータのある画面が現れます。

このようにNext.jsはprefetch機能はありますが、機能するのは静的なページのところまでです。動的なコンテンツはprefetchされません。動的コンテンツが多いサイトの場合はNext.jsのprefetchは効果がかなり限定的になります。なお、本サイトは動的コンテンツのサイトを作成している開発者を念頭にしています。そのため、App routerのキャッシュは極力オフにしており、すべてのページはDynamic renderingさせています。

キャッシュの効果 #

Turbo Driveには仕組みをほとんど理解していなくても安心して使用できるキャッシュがあります。

以前に訪問したページに遷移すると、Turbo Driveは以前のページ内容をプレビューとして表示します(キャッシュから表示)。そして同時にサーバにリクエストを投げ、サーバから最新のページを受け取ると、すぐにプレビューの内容と入れ替えます。

Next.jsもキャッシュがありますが、キャッシュの更新処理がわかりにくく、古い情報が残ってしまいやすい問題がありました。評判が悪いために、v15ではデフォルトがキャッシュオフになりました。Next.jsのキャッシュは特別なチューニングが必要なレーシングカーのようなもので、仕組みを熟知している開発者が丁寧にウェブサイトを作る場合は非常に効果的ですが、通常利用では取り扱いに注意を要するものでした。

一方でTurbo Driveのキャッシュの場合は古い情報は一時的に表示されますが、すぐに最新情報に更新されます。サーバ負荷、ネットワーク負荷低減効果はありませんが、優れたUI/UXに加え、開発者として扱いやすい利点があります。

ビデオ #

まとめの図 #

各種の技術を使用した時の画面遷移の違い
Page Transition image