Firebase Cloud Messaging (FCM) is the standard for delivering web push notifications. The legacy CDN-based setup (Firebase v7) is deprecated. This guide uses the v10 modular SDK with Vue 3, smaller bundles, full TypeScript support, and the correct service worker pattern for 2026.
Project Setup
pnpm add firebase
# Or: npm install firebase
Create src/lib/firebase.ts:
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
export const app = initializeApp(firebaseConfig);
export const messaging = getMessaging(app);
Store all values in .env.local, never hardcode Firebase credentials in source.
Step 1: Get a VAPID Key
- Firebase Console → Project Settings → Cloud Messaging
- Under Web configuration, click Generate key pair
- Copy the public key, you’ll use it as
VITE_FIREBASE_VAPID_KEY
Step 2: Request Permission & Get FCM Token (Vue 3 Composable)
Create src/composables/usePushNotifications.ts:
export function usePushNotifications() {
const token = ref<string | null>(null);
const notification = ref<MessagePayload | null>(null);
const error = ref<string | null>(null);
async function requestPermissionAndGetToken() {
try {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
error.value = 'Notification permission denied';
return;
}
// Register the service worker first
const registration = await navigator.serviceWorker.register(
'/firebase-messaging-sw.js'
);
token.value = await getToken(messaging, {
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
serviceWorkerRegistration: registration,
});
console.log('FCM token:', token.value);
// TODO: send token.value to your backend for storage
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
}
}
// Handle foreground messages (when the app tab is open)
function listenForMessages() {
onMessage(messaging, (payload) => {
console.log('Foreground message received:', payload);
notification.value = payload;
});
}
return { token, notification, error, requestPermissionAndGetToken, listenForMessages };
}
Use it in a component:
<script setup lang="ts">
const { token, notification, error, requestPermissionAndGetToken, listenForMessages } =
usePushNotifications();
onMounted(() => {
listenForMessages();
});
</script>
<template>
<div>
<button @click="requestPermissionAndGetToken">Enable Notifications</button>
<p v-if="error" class="text-red-500">{{ error }}</p>
<p v-if="notification">{{ notification.notification?.title }}</p>
</div>
</template>
Step 3: Create the Service Worker
Create public/firebase-messaging-sw.js (must be at the web root):
// public/firebase-messaging-sw.js
import {
getMessaging,
onBackgroundMessage,
} from 'https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-sw.js';
const firebaseConfig = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_AUTH_DOMAIN',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_STORAGE_BUCKET',
messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
appId: 'YOUR_APP_ID',
};
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
onBackgroundMessage(messaging, (payload) => {
console.log('Background message received:', payload);
const { title = 'New message', body = '' } =
payload.notification ?? {};
self.registration.showNotification(title, {
body,
icon: '/icon-192x192.png',
badge: '/badge-72x72.png',
data: payload.data,
});
});
Note: Service workers cannot use Vite environment variables. Inline the Firebase config here directly or use a build step to inject them at deploy time.
Step 4: Send a Notification from Node.js (Backend)
Install the Admin SDK:
npm install firebase-admin
// server/send-notification.ts
initializeApp({
credential: cert('./service-account.json'),
});
const messaging = getMessaging();
async function sendPushToUser(fcmToken: string) {
const messageId = await messaging.send({
token: fcmToken,
notification: {
title: 'Your order shipped! 📦',
body: 'Estimated delivery: tomorrow between 2–5 PM',
},
webpush: {
fcmOptions: {
link: 'https://yourapp.com/orders',
},
},
});
console.log('Sent message:', messageId);
}
For sending to a topic or user segment, use messaging.sendEachForMulticast() with an array of tokens.
Update manifest.json
Add the FCM sender ID to your web app manifest:
{
"name": "My App",
"gcm_sender_id": "YOUR_MESSAGING_SENDER_ID"
}
Summary
| Step | What it does |
|---|---|
initializeApp() |
Connects to your Firebase project |
Notification.requestPermission() |
Prompts the browser permission dialog |
getToken(messaging, { vapidKey }) |
Returns the FCM registration token |
onMessage() |
Handles foreground notifications |
firebase-messaging-sw.js |
Handles background/closed-tab notifications |
messaging.send() (Admin SDK) |
Sends notifications from your server |
The token returned by getToken() is what your server sends to. Store it per-user in your database and refresh it whenever the user revisits the app.
Top comments (0)