Build1 distinct publisher3 min readUpdated
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

Compiled by The EngineerSomething wrong?How this is made
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].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
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.
Recommended feature detection: canPrompt = 'PushManager' in window && (!isIOS || isStandalone), where isStandalone comes from window.matchMedia('(display-mode: standalone)').matches; when an iOS user is browsing in a tab, show install instructions instead of a button that does nothing.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Single first-hand implementation report, technically checkable but uncorroborated
Every claim traces to one dev.to field report by a developer who shipped the feature, with working code and specific, falsifiable API behaviors (scope rules, runtime/crypto dependency, 404/410 semantics, the canPrompt guard). That is stronger than opinion but there is no second publisher, vendor release note, or spec citation in the cluster to confirm the iOS gate or the Declarative Web Push payload shape, so it stays mid-band.
Capability shipped by both major engines; usage breadth unmeasured
Adoption is evidenced at the platform layer: Web Push has been in iOS since 16.4 (March 2023) and Safari 18.4 added Declarative Web Push (March 2025), while Chrome and Firefox behavior is described as established, and one concrete Next.js 16 deployment is disclosed. What is missing is any measure of how many products actually run web push or how many iOS users complete the install step, so breadth cannot be scored higher.
Mostly aligned; framing slightly oversells the documentation gap
The technical substance matches the evidence and is deliberately unglamorous, so there is almost no inflation. The small positive comes from framing devices — 'nobody documents loudly enough' and 'silently blocks everything' — asserting an industry-wide documentation failure that the cluster does not evidence, since the iOS install requirement has been a stated platform condition since 16.4 per the article's own timeline.
Individual practitioner post with minimal commercial pull
The author is a named developer publishing on a community platform, and the piece actively removes vendor dependency by noting no SDK sits in the critical path and that Firebase Cloud Messaging is merely one browser's endpoint. Residual incentive is limited to authorship visibility and incidental platform-favorable asides such as Vercel's Node.js runtime default on Fluid Compute, so the score stays low.
Confident on mechanics, thin on corroboration and impact
The API-level claims are internally consistent, dated, and easy for a reader to verify, which supports moderate confidence. It is capped by the cluster being a single practitioner voice with no second publisher, no spec or vendor citation, and no data on the business consequence of the iOS install gate.
build
Your Next.js rate limiter counts per instance, and Server Actions hide behind the page URL1 distinct publisher
build
Next.js's architecture is working. Its operating manual isn't keeping up.1 distinct publisher
build
The MCP test that matters: a log tool that fetched the data and then said it failed1 distinct publisher
build
height:auto is animatable now, so your max-height ceiling is a bug you can delete1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026