Nothing says "desperate" like a notification prompt the instant someone opens your site. Users don't know you yet, don't trust you, and will click Block without thinking. Now your notification features are dead for that user forever.
Your code calls Notification.requestPermission() during page load - before the user has expressed any interest in receiving notifications. The browser shows a permission prompt, the user blocks it reflexively, and that decision persists until they manually dig through browser settings to reverse it.
Chrome has started hiding these prompts entirely for sites that abuse them. Your notification request might not even be shown anymore if your site has a poor grant rate.
Check the Lighthouse audit details for the exact source location. To find it manually:
Cmd/Ctrl+Shift+F) for Notification.requestPermission or pushManager.subscribe// Bad: Runs immediately
Notification.requestPermission()
// Bad: Runs on page load
document.addEventListener('DOMContentLoaded', () => {
Notification.requestPermission()
})
// Bad: In module top-level
if (Notification.permission === 'default') {
Notification.requestPermission()
}
// Bad: Auto-runs after slight delay
setTimeout(() => Notification.requestPermission(), 3000)
Move the permission request inside a user-triggered event:
Before (bad):
// Runs on page load
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
subscribeToNotifications()
}
})
After (good):
document.querySelector('#enable-notifications').addEventListener('click', () => {
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
subscribeToNotifications()
updateButtonState('enabled')
}
else {
updateButtonState('denied')
}
})
})
The user clicks a button that says "Enable notifications," the browser prompt appears, and context is clear.
Show users what they'll get before triggering the browser prompt:
const notifyBtn = document.querySelector('#notify-toggle')
notifyBtn.addEventListener('click', async () => {
const permission = Notification.permission
if (permission === 'granted') {
// Already enabled, maybe show settings
showNotificationSettings()
}
else if (permission === 'denied') {
// Blocked - explain how to unblock
showUnblockInstructions()
}
else {
// Show our own explainer first
showNotificationExplainer()
}
})
function showNotificationExplainer() {
const modal = document.querySelector('#notification-explainer')
modal.innerHTML = `
<h3>Get notified when:</h3>
<ul>
<li>Your order ships</li>
<li>Someone replies to your comment</li>
<li>There's a flash sale</li>
</ul>
<button id="confirm-notify">Enable Notifications</button>
<button id="skip-notify">Not Now</button>
`
modal.showModal()
modal.querySelector('#confirm-notify').addEventListener('click', () => {
modal.close()
Notification.requestPermission().then(handlePermissionResult)
}, { once: true })
}
Request after the user has shown investment in your site:
// Only show notification option after user has engaged
let userEngaged = false
function checkEngagement() {
// User has been on site 2+ minutes, or completed an action
const timeOnSite = Date.now() - pageLoadTime
const hasInteracted = purchaseComplete || commentPosted || signedUp
if (timeOnSite > 120000 || hasInteracted) {
userEngaged = true
showNotificationPrompt()
}
}
function showNotificationPrompt() {
if (Notification.permission !== 'default')
return
// Show soft prompt, not browser prompt
const banner = document.querySelector('#notification-banner')
banner.classList.remove('hidden')
banner.querySelector('#enable-btn').addEventListener('click', () => {
Notification.requestPermission()
banner.classList.add('hidden')
}, { once: true })
}
Run Lighthouse - the "Requests the notification permission on page load" audit should pass.
setTimeout(() => Notification.requestPermission(), 5000) still runs automatically. Five seconds isn't user engagement; it's just slower annoyance.Notification requests might be triggered by scripts that only load on specific pages, like checkout or account settings. Unlighthouse scans your entire site and identifies every page requesting notification permission on load.
HTTPS
Eliminate insecure HTTP requests and mixed content warnings. Learn to enforce HTTPS across your entire site for security and modern web features.
Paste Inputs
Stop preventing users from pasting into input fields. Blocking paste breaks password managers and frustrates users without improving security.