Skip to content

Build1 publisher3 min readPublished

The fourth Web Push requirement: iOS will not deliver until the user installs your site

A dev.to field report on Next.js App Router push finds iOS Safari exposes the Push API only to Home Screen installs, which makes the install prompt part of the notification stack.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Photograph accompanying The fourth Web Push requirement: iOS will not deliver until the user installs your site
Photo: webkit.org

What happened

  • Web Push in a Next.js App Router project needs exactly three parts: a service worker served from the origin root, a VAPID key pair, and a Node.js-runtime route handler that sends.
  • On iOS Safari, Web Push needs a fourth thing that is not loudly documented: the user must install the site to the Home Screen first.
  • iOS Safari 16.4 and later support Web Push, but only after the user adds the site to the Home Screen and the app runs in display: standalone mode; in a normal iOS tab, window.PushManager is undefined.
  • Web Push landed in iOS 16.4 in March 2023 with exactly this Home Screen constraint, and it still holds.
  • In a normal Safari tab on iOS the permission prompt never appears: no error, no rejected promise, nothing to debug.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A field report published on dev.to describes wiring Web Push into a Next.js 16 App Router project as three moving parts, a service worker served from the origin root, a VAPID key pair, and a Node.js-runtime route handler that sends, then adds a fourth condition that applies only to iOS: the user must install the site to the Home Screen first [1][2]. That reclassifies push from an infrastructure choice to a product flow choice, because no amount of server work substitutes for a user tapping Add to Home Screen.

The failure mode is the expensive part. On iOS Safari 16.4 and later, the Push API is exposed only after the site is added to the Home Screen and runs in `display: standalone` mode; in a normal tab, `window.PushManager` is undefined [3]. According to the author, that means no error, no rejected promise, and no permission prompt, so there is nothing in the console to debug [5]. The constraint arrived with Web Push in iOS 16.4 in March 2023 and still holds [4]. Their handling is to gate the button on `'PushManager' in window && (!isIOS || isStandalone)` and show install instructions where the button would be [6]. The manifest also has to declare `"display": "standalone"`, or the installed shortcut reopens inside Safari chrome and never qualifies [7].

The other three requirements are more conventional but each has a sharp edge. The worker has to sit at `public/sw.js` so Next.js serves it from `/sw.js`, because a service worker's scope can never be broader than the path it was served from, and a worker served from `/_next/static/` would control no pages at all [8]. Files in `public/` are copied verbatim and never bundled, so npm imports are not available in that file [9]. VAPID keys are generated once with `npx web-push generate-vapid-keys` and should not be rotated casually, since they are what authenticates your server to the browser's push service [10]. The send path must run on the Node.js runtime rather than Edge, because the `web-push` package uses Node's `crypto` for aes128gcm payload encryption [11].

Three operational details in the report are worth copying directly. A push service returning 404 or 410 means the subscription is permanently dead and the row should be deleted immediately [12]. `Notification.requestPermission()` has to be called inside a user gesture, a click handler rather than an effect [13]. And `userVisibleOnly: true` is a promise to display a notification for every push received, which Chrome will eventually enforce by revoking a subscription that repeatedly breaks it [14]. Chrome and Firefox accept a base64url `applicationServerKey`, while older Safari builds and some Android WebViews still want a `Uint8Array`, which is why production code tends to keep a conversion helper [15]. The author also notes there is no vendor SDK in the critical path; Firebase Cloud Messaging is one browser's push endpoint, not a protocol requirement [16].

The honest planning consequence is that iOS delivery rates are bounded by install rates, not by permission grants. If the product has no reason for someone to keep an icon on their Home Screen, push on iOS is a channel you can build and still not reach anyone through. The author's own accounting is a useful calibration: the happy path took an afternoon, and the rest of the week went to iOS, to subscriptions that had died months earlier, and to a service worker the browser refused to replace [17].

Watch Safari 18.4, shipped in March 2025, which added Declarative Web Push, where the server sends JSON containing a `web_push` key set to `8030` plus a `notification` object and the browser renders it without running your service worker's push handler [18]. It is additive, and the classic service worker path is still required for Chrome [18]. Two years after the Home Screen rule shipped, Apple has made rendering simpler without touching the install prerequisite [19].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories