manifest.json / manifest.ts
Describe the app to browsers with a static web manifest or a function returning one.
export default function manifest(): MetadataRoute.Manifest- Added in Next.js 13.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$nameoptional | string | Full application name, shown on the install prompt and splash screen. |
$short_nameoptional | string | Shorter name used where space is tight, such as under a home-screen icon. |
$descriptionoptional | string | Explanation of the app, shown in some install UIs. |
$start_urloptional | string | URL opened when the installed app is launched, resolved against the manifest's own URL. |
$displayoptional | 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser' | How much browser chrome the launched app keeps. |
$background_coloroptional | string | CSS colour painted on the splash screen before the app renders. |
$theme_coloroptional | string | CSS colour applied to the surrounding OS or browser UI. |
$iconsoptional | Array<{ src: string; sizes?: string; type?: string; purpose?: string }> | Icons the platform picks from when installing. Installability generally needs at least a 192x192 and a 512x512 entry. |
Return value
MetadataRoute.ManifestA Manifest object matching the Web Application Manifest specification, which Next.js serialises to JSON and serves from the app root.
Errors
Next.js does not validate the object against the Web Manifest specification; unsupported or misspelled members are serialised as given and ignored by the browser.
Changelog
- PHP 13.3.0manifest file convention introduced as part of the file-based Metadata API.
A web manifest tells the browser what your site is called, what to open when it launches, and which icons to use on a home screen. Next.js accepts a static app/manifest.json or app/manifest.webmanifest, or a app/manifest.ts that returns the same object typed as MetadataRoute.Manifest — and injects the <link rel="manifest"> tag for you.
When to reach for the code version
- Name and theme colour differ per white-label tenant or per environment.
- You want the icon list built from constants shared with the rest of the app.
- Values come from the same config that feeds your metadata exports.
What trips people up
The file must sit at the root of app/. Nested manifests are not a thing, unlike icons and share images, which cascade per segment.
A manifest alone does not make a site installable. Browsers also want HTTPS, a start_url that actually loads, display set to something other than browser, and icons at 192×192 and 512×512. Chrome's install prompt stays hidden until all of those hold, and the DevTools Application panel is where it tells you which one is missing. Icon src values are resolved relative to the manifest URL, so absolute paths are the safe choice.
Example
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Next.js App',
short_name: 'Next.js App',
description: 'Next.js App',
start_url: '/',
display: 'standalone',
background_color: '#fff',
theme_color: '#fff',
icons: [
{
src: '/favicon.ico',
sizes: 'any',
type: 'image/x-icon',
},
],
}
}When to use it
- Make an internal dashboard installable on staff phones
- Set the splash screen and status-bar colours for a standalone launch
- Swap the app name and theme colour per white-label tenant at build time
- Keep the icon list in sync with the PNGs referenced elsewhere in the app
Frequently asked questions
Where does manifest.json go in a Next.js app router project?
At the root of the app directory — app/manifest.json, app/manifest.webmanifest or app/manifest.ts. Unlike icons and share images it is not per-segment, and a copy left in public/ competes for the same URL.
Why is my Next.js PWA not showing an install prompt?
The manifest alone is not enough: browsers also require HTTPS, a start_url that loads, display set to standalone, minimal-ui or fullscreen, and icons at both 192x192 and 512x512. Chrome DevTools' Application panel lists exactly which requirement is failing.
Should I use manifest.ts or a static manifest.json?
Use manifest.ts when values vary by environment or tenant, or when you want MetadataRoute.Manifest to type-check the keys. A static .json is fine for a fixed manifest and avoids running any code at build time.
More examples
An installable manifest with the required icon sizes
The 192 and 512 pair is what Chrome checks before offering an install prompt; the single favicon.ico of the minimal example is not enough. The maskable entry lets Android crop the icon into its own shape without clipping the logo. Every src is an absolute path to a file in public/icons, so it resolves the same wherever the manifest is served from.
// app/manifest.ts
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Acme Field Reports',
short_name: 'Acme Field',
description: 'Log site visits offline and sync when you reconnect.',
start_url: '/reports',
display: 'standalone',
orientation: 'portrait',
background_color: '#0b0b0b',
theme_color: '#2563eb',
icons: [
{
src: '/icons/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/icons/maskable-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
}
}Per-environment name and colour
Installing both builds on one phone gives two home-screen entries with different names and a different status-bar colour, so nobody files a bug against staging thinking it is production. The environment variable is read at module scope, so the value is fixed per deployment rather than per request.
// app/manifest.ts
import type { MetadataRoute } from 'next'
const isProduction = process.env.VERCEL_ENV === 'production'
export default function manifest(): MetadataRoute.Manifest {
return {
name: isProduction ? 'Acme' : 'Acme (staging)',
short_name: isProduction ? 'Acme' : 'Acme stg',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: isProduction ? '#2563eb' : '#f59e0b',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
],
}
}Static manifest file
Identical output to the code version, with no build-time execution and no TypeScript checking — which also means a typo like "start-url" ships silently. The .ts variant catches that against MetadataRoute.Manifest, which is the main reason to prefer it once the file grows beyond a handful of keys.
// app/manifest.json
//
// {
// "name": "My Next.js Application",
// "short_name": "Next.js App",
// "description": "An application built with Next.js",
// "start_url": "/",
// "display": "standalone",
// "background_color": "#ffffff",
// "theme_color": "#000000",
// "icons": [
// { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
// { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
// ]
// }
//
// app/manifest.webmanifest is accepted with identical contents.
export {}
Discussion