スマートフォンでサイトを「ホーム画面に追加」したとき、アイコンの下に表示される名前や、タップして起動したときにアドレスバーが出るかどうかは、Web App Manifest という JSON ファイルで決まります。Next.js の App Router では、app 直下に manifest.ts を置いて決められた形のオブジェクトを返すだけで、この JSON を生成して配信してくれます。この記事では manifest.ts の基本の書き方、主なフィールドの意味、display や icons の指定方法、そして設定が反映されないときの確認手順までを解説します。
目次
Web App Manifest とは何か
Web App Manifest は、ウェブサイトを「アプリのように」扱うための情報をまとめた JSON ファイルです。W3C が仕様を定めており、ブラウザはこのファイルを読んで、ホーム画面に追加されたときのアイコン・アプリ名・起動 URL・起動時の表示スタイルなどを決めます。いわゆる PWA(Progressive Web App)としてインストール可能にするための土台にあたる部分です。
ふだんの HTML では、次のように <head> の中で link 要素から参照します。Next.js のファイル規約を使う場合、この link 要素はフレームワークが自動で出力してくれるので、自分で書く必要はありません。
<link rel="manifest" href="/manifest.webmanifest" />
注意したいのは、manifest を置いただけでサイトが自動的にアプリになるわけではない点です。manifest はあくまで「インストールされたときにどう見せるか」を宣言するもので、オフライン動作などは Service Worker の役割です。とはいえ、ホーム画面に追加したときの見栄えを整えるだけでも効果は大きいので、まずは manifest から用意するのが順当な進め方です。
app/manifest.ts で manifest を生成する
App Router では、app ディレクトリの直下(ルート)に manifest.ts を作り、MetadataRoute.Manifest 型のオブジェクトを返す関数を default export します。これだけで /manifest.webmanifest という URL で JSON が配信され、各ページの <head> に link 要素が差し込まれます。
import type { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Webool サンプルアプリ',
short_name: 'Webool',
description: 'Next.js で作ったサンプルアプリケーションです。',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#1e3a8a',
icons: [
{
src: '/icons/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
],
};
}
置き場所は必ず app/manifest.ts です。app/(marketing)/manifest.ts のようにルートグループの中に入れると認識されないので、ネストしたフォルダではなく app の直下に置いてください。また、MetadataRoute.Manifest は next パッケージが提供する型で、import type で読み込んで戻り値の型として指定しておくと、フィールド名のタイプミスや値の間違いをエディタ上で検出できます。
この manifest.ts は内部的には特殊なルートハンドラーとして扱われ、既定ではキャッシュされます。つまり、内容がビルド時に固定される前提の書き方が基本です。
主なフィールドと役割
manifest には多くのフィールドがありますが、まず押さえておきたいのは次のものです。仕様上はすべて任意の項目ですが、インストール可能な PWA として扱ってもらうには、名前・起動 URL・アイコンあたりは実質必須と考えておくとよいでしょう。
| フィールド | 役割 |
|---|---|
name | アプリの正式名称。インストール時のダイアログやスプラッシュ画面で使われる |
short_name | 短縮名。ホーム画面のアイコン下など、表示スペースが狭い場所で使われる |
description | アプリの説明文。インストール画面やアプリ一覧で表示されることがある |
start_url | アイコンから起動したときに最初に開く URL。ふつうは '/' |
display | 起動時の表示スタイル。ブラウザ UI をどこまで出すかを決める |
background_color | 起動直後、CSS が適用されるまでの背景色(スプラッシュ画面の色) |
theme_color | アプリのツールバーやシステム UI に使われるテーマ色 |
icons | ホーム画面などに表示するアイコンの配列 |
name と short_name の使い分けは意外と重要です。ホーム画面のアイコン下は文字数が限られるため、name が長いと途中で省略されてしまいます。short_name には 12 文字程度に収まる短い名前を入れておくと、どの端末でも読める表示になります。
background_color はスプラッシュ画面の色に使われるので、サイトの背景色と揃えておくと起動時のちらつきが目立ちません。ここが白なのにサイトがダーク基調だと、起動のたびに白い画面が一瞬挟まることになります。
display で起動時の見え方を切り替える
display は、ホーム画面のアイコンから起動したときにブラウザの UI をどこまで表示するかを決めるフィールドです。指定できる値は次の4つです。
| 値 | 表示のされ方 |
|---|---|
'fullscreen' | 画面全体を使い、ブラウザの UI をいっさい表示しない。ゲームや動画向き |
'standalone' | 独立したアプリのウィンドウで開く。アドレスバーは表示されない。PWA でもっとも一般的 |
'minimal-ui' | standalone に近いが、戻る・再読み込みなど最小限のブラウザ操作が残る |
'browser' | 通常のブラウザのタブ/ウィンドウとして開く |
迷ったら 'standalone' を選べば問題ありません。ネイティブアプリのように見せつつ、ブラウザ側の戻るジェスチャーなどは使えるため、多くのサイトで扱いやすい設定です。
ただし、指定した値が必ずそのとおりになるとは限りません。display はブラウザや OS が対応していない場合、より制約の緩い表示にフォールバックする仕組みになっています。たとえば 'fullscreen' に対応していない環境では 'standalone'、さらに対応していなければ 'minimal-ui'、最終的に 'browser' という順に下がります。そのため「最低でもこの見え方は許容できる」というラインを意識して選ぶのが安全です。
icons の書き方と用意するサイズ
icons は配列で、1つのアイコンにつき src(画像のパス)・sizes(ピクセルサイズ)・type(MIME タイプ)を指定します。ブラウザは表示したいサイズに近いものをこの配列から選ぶので、複数のサイズを並べておくのが基本です。
icons: [
{
src: '/icons/icon-192.png', // public/icons/icon-192.png に置いたファイル
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
{
// 端末側で丸く切り抜かれても欠けないよう余白を持たせた画像
src: '/icons/icon-512-maskable.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
用意するサイズは 192×192 と 512×512 の 2 種類を基本にしてください。192px はホーム画面のアイコンに、512px はスプラッシュ画面やアプリ一覧など大きく表示される場面に使われます。この2つがあれば、多くの環境で「インストール可能」と判断されます。
src に書くのは、公開後の URL から見たパスです。画像を public/icons/icon-192.png に置いたなら '/icons/icon-192.png' と書きます。'./icons/...' のような相対パスは、閲覧しているページによって解決先が変わってしまうので、先頭にスラッシュを付けた絶対パスで書くのが確実です。
purpose は、そのアイコンをどう使ってほしいかの指定です。既定は 'any' で、そのまま表示されます。'maskable' を指定すると、端末側で円形や角丸などの形に切り抜いて使われる前提の画像として扱われます。Android では独自の形にトリミングされるため、ロゴの周囲に十分な余白を持たせた maskable 用の画像を別途用意しておくと、端末によってロゴが切れる問題を避けられます。
theme_color と viewport のテーマカラーの違い
紛らわしいのが、色の指定が2か所にあることです。manifest の theme_color は「インストールされたアプリのウィンドウやシステム UI の色」で、ブラウザで普通に閲覧しているときのアドレスバーの色を決めるのは <meta name="theme-color"> のほうです。
Next.js の App Router では、この meta 要素は layout.tsx の viewport または generateViewport から出力します。両者は別々の設定なので、片方だけ変えると通常閲覧時とインストール時で色が食い違います。基本的には同じ値を書いておきましょう。viewport 側の詳しい書き方は別記事で解説しています。
import type { Viewport } from 'next';
// manifest.ts の theme_color と同じ値を指定しておく
export const viewport: Viewport = {
themeColor: '#1e3a8a',
};
静的な manifest.json との使い分け
manifest は TypeScript で生成せず、静的なファイルとして置くこともできます。app/manifest.json(または app/manifest.webmanifest)を作れば、Next.js がそれを配信し、link 要素も同じように挿入してくれます。
{
"name": "Webool サンプルアプリ",
"short_name": "Webool",
"description": "Next.js で作ったサンプルアプリケーションです。",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1e3a8a",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
内容がまったく変わらないのであれば、静的ファイルのほうがシンプルで分かりやすい選択です。一方 manifest.ts にすると、環境変数やアプリ内の定数を使って値を組み立てられます。サイト名を一箇所で管理していて manifest とページタイトルの両方で使いたい、といったケースでは TypeScript 版が有利です。型チェックが効くのも利点で、display に存在しない値を書けばエディタが警告してくれます。
なお、public/manifest.json に置いても静的ファイルとしては配信されますが、こちらはファイル規約の対象外なので link rel="manifest" は自動では出力されません。その場合はメタデータの設定などで自分でリンクを追加する必要があります。特別な理由がなければ、app 直下に置く方法を選んでください。
manifest が読み込まれない・アイコンが表示されないとき
まずブラウザの DevTools で実際の中身を見る
推測で直そうとせず、Chrome の DevTools を開いて Application タブ(Firefox なら「アプリケーション」パネル)の Manifest を確認するのが近道です。ここにはブラウザが実際に解釈した manifest の内容と、読み込んだアイコンのプレビュー、そして「インストール可能かどうか」の判定結果とエラーメッセージが表示されます。アイコンの取得に失敗していれば、その旨がここに出ます。
Manifest の項目に何も表示されない場合は、そもそも link rel="manifest" が出力されていないか、URL が 404 になっています。ブラウザで /manifest.webmanifest を直接開いて、JSON が返ってくるかを確かめてみてください。
ファイルの置き場所が間違っている
/manifest.webmanifest が 404 になるときは、まずファイルの位置を疑います。manifest.ts が有効なのは app ディレクトリの直下だけです。app/(site)/manifest.ts や src/manifest.ts では読み込まれません。src ディレクトリ構成を使っている場合は src/app/manifest.ts が正しい位置です。
また、関数は必ず default export である必要があります。export function manifest() のように名前付きでエクスポートしても認識されません。
アイコンのパスが間違っている
manifest 自体は正しく配信されているのにアイコンだけ出ない、というのはよくあるつまずきです。原因のほとんどは src のパス誤りです。DevTools の Network タブで、アイコン画像のリクエストが 200 で返っているかを確認してください。404 になっていれば、public 以下の実際のファイル構成とパスがずれています。public/icons/icon-192.png は /icons/icon-192.png であって、/public/icons/icon-192.png ではない点に注意しましょう。
sizes の値と画像の実寸が食い違っているケースもあります。sizes: '512x512' と書いてあるのに実際は 192px の画像だと、大きく表示される場面でぼやけたり、インストール条件を満たさないと判定されたりします。
古い内容がキャッシュされている
manifest.ts は既定でキャッシュされるルートハンドラーとして扱われるため、値を書き換えたのに反映されないように見えることがあります。開発中は開発サーバーを再起動し、ブラウザ側でもスーパーリロード(キャッシュを無視した再読み込み)を試してください。DevTools の Application タブから該当サイトのストレージをクリアするのも有効です。
すでにホーム画面に追加済みの端末では、さらに厄介です。インストール時点の manifest の内容が保持されるため、アイコンやアプリ名を変えても、いったんアンインストールして追加し直さないと反映されないことがあります。実機で確認するときは、一度削除してから再度追加してみてください。
まとめ
Web App Manifest は、サイトをホーム画面に追加したときのアプリ名・アイコン・起動時の見た目を定義する JSON ファイルです。Next.js の App Router なら、app 直下に manifest.ts を置いて MetadataRoute.Manifest 型のオブジェクトを返す関数を default export するだけで、/manifest.webmanifest として配信され、link rel="manifest" も自動で出力されます。name と short_name は表示場所によって使い分けられ、display は 'standalone' を選んでおけばアプリらしい見え方になります。アイコンは 192×192 と 512×512 を用意し、必要に応じて purpose: 'maskable' の画像も添えるのが定番です。アドレスバーの色を決める meta name="theme-color" は viewport 側の設定なので、manifest の theme_color と値を揃えておきましょう。反映されないときは、まず DevTools の Application タブで実際に読み込まれた内容とエラーを確認するのが解決への近道です。