Membangun Web Push Notification Tangguh dengan Next.js, Cloudflare Edge, dan OneSignal

Arsitektur notifikasi Next.js dengan Cloudflare dan OneSignal
Panduan implementasi arsitektur notifikasi dorong yang 100% kompatibel dengan Edge Runtime Cloudflare Pages menggunakan Next.js D1 dan REST API OneSignal.
Retensi pengguna dan keterlibatan audiens adalah urat nadi bagi setiap portal publikasi digital dan platform edukasi modern. Saat audiens membaca sebuah konten yang bernilai tinggi, Anda tentu ingin mereka kembali lagi ketika ada pembaruan. Cara paling efektif untuk mencapai hal ini bukanlah dengan menunggu mereka mengingat untuk membuka kembali situs Anda, melainkan dengan menjemput mereka melalui notifikasi langsung di layar perangkat mereka.
Banyak pengembang awalnya mencoba membangun Native Web Push (menggunakan VAPID dan pustaka web-push) agar tidak bergantung pada pihak ketiga. Namun, ketika mendeploy aplikasi Next.js ke Cloudflare Pages, kita dihadapkan pada satu tembok arsitektur: Edge Runtime. Lingkungan Edge Cloudflare sangat cepat dan ringan, tetapi ia tidak memiliki modul kriptografi inti Node.js (seperti crypto) yang dibutuhkan oleh sistem Native Web Push.
Kompromi infrastruktur (best practice) terbaik untuk mengatasi benturan ini adalah mendelegasikan beban kriptografi ke layanan khusus seperti OneSignal. Artikel ini akan memandu Anda langkah demi langkah mengimplementasikan sistem notifikasi menggunakan React OneSignal, REST API ringan yang disukai Edge Runtime, dan database Cloudflare D1 untuk pencatatan aktivitas pengguna.
Memahami Arsitektur Cloudflare Edge dan OneSignal
Dalam arsitektur ini, kita membagi tugas sistem menjadi tiga bagian ideal:
- Cloudflare D1: Menyimpan profil pengguna (email, nama) yang secara sadar mengklik tombol "Follow" di website kita.
- OneSignal: Menyimpan token perangkat keras (browser subscription) dan menangani proses enkripsi kriptografi berat ke server Google/Apple.
- Cloudflare Pages (Edge API): Mengirimkan perintah teks ringan (JSON) via
fetchREST API ke OneSignal saat kita ingin mem-broadcast artikel baru.
Kombinasi ini menjamin proses build (deploy) di Cloudflare sukses 100% tanpa adanya error 500 akibat hilangnya modul Node.js.
Langkah 1: Instalasi Library dan Registrasi Kunci
Langkah pertama adalah mendaftar ke paket gratis OneSignal. Setelah mendaftar dan membuat aplikasi bertipe "Web", buka menu Settings -> Keys & IDs. Dapatkan dua kode krusial berikut:
- App ID: Identitas publik aplikasi Anda.
- REST API Key: Kunci rahasia untuk mengirim perintah notifikasi.
Selanjutnya, buka terminal pada editor VS Code Anda dan instal pustaka resmi OneSignal untuk ekosistem React:
npm install react-onesignal
Langkah 2: Mempersiapkan Skema Database Cloudflare D1
Meski OneSignal menyimpan token, kita tetap membutuhkan tempat mencatat siapa saja pengguna (Google Login) yang berinteraksi. Masuk ke tab Console pada Cloudflare D1 SQL Database Anda, dan eksekusi kueri berikut jika Anda belum memiliki kolom untuk push:
ALTER TABLE followers ADD COLUMN push_sub TEXT;
Langkah 3: Konfigurasi Service Worker OneSignal
Beralih dari Service Worker manual, OneSignal menyediakan skrip CDN otomatis yang lebih mutakhir. Di dalam proyek Next.js Anda, buat sebuah file baru bernama OneSignalSDKWorker.js persis di dalam folder public.
Isi file tersebut HANYA dengan satu baris kode sakti ini:
importScripts("https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.sw.js");
Langkah 4: Mengintegrasikan Tombol Izin (Follow) ke Antarmuka
Alih-alih memunculkan bel floating bawaan OneSignal yang terkadang merusak desain visual web, kita akan mengaitkan pemicu notifikasi secara elegan ke dalam komponen tombol "Ikuti" (Follow). Panggilan pop-up OneSignal baru akan muncul secara psikologis setelah user secara sadar menekan tombol tersebut.
Ubah file komponen sidebar/footer Anda (misalnya components/sidebar-footer.tsx) dengan kode TypeScript komprehensif berikut:
"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useSession, signIn } from "next-auth/react";
import { Instagram, Youtube, Facebook, UserPlus, Check } from "lucide-react";
import OneSignal from 'react-onesignal';
export default function SidebarFooter() {
const { data: session, status } = useSession();
const isLoggedIn = status === 'authenticated';
const [isFollowing, setIsFollowing] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// Inisiasi OneSignal secara aman di sisi Klien (Browser)
useEffect(() => {
if (typeof window !== "undefined") {
OneSignal.init({
appId: "APP_ID_ONESIGNAL_ANDA_DISINI", // Ganti dengan App ID Anda
allowLocalhostAsSecureOrigin: true
}).catch(console.error);
}
}, []);
// Cek Status Follow saat halaman dimuat
useEffect(() => {
async function checkFollowStatus() {
if (!session?.user?.email) {
setIsLoading(false);
return;
}
try {
const res = await fetch('/api/follow');
const data = await res.json();
if (Array.isArray(data)) {
const hasFollowed = data.some((f: any) => f.email === session.user?.email);
if (hasFollowed) setIsFollowing(true);
}
} catch (error) {
console.error("Gagal mengecek status:", error);
} finally {
setIsLoading(false);
}
}
checkFollowStatus();
}, [session]);
// Cek Auto-Follow pasca Login Google
useEffect(() => {
if (isLoggedIn && session?.user) {
const pendingFollow = localStorage.getItem("pending_follow");
if (pendingFollow === "true" && !isFollowing) {
eksekusiFollow(session.user);
localStorage.removeItem("pending_follow");
}
}
}, [isLoggedIn, session, isFollowing]);
const eksekusiFollow = async (user: any) => {
if (!user?.email) return;
setIsFollowing(true);
const newFollower = {
email: user.email,
name: user.name || "Pengguna",
avatar: user.image || `https://ui-avatars.com/api/?name=${user.name || 'P'}&background=0D8ABC&color=fff`,
push_sub: null // Token ditangani langsung oleh server OneSignal
};
try {
await fetch('/api/follow', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newFollower)
});
// Memicu Slidedown Izin OneSignal yang elegan
if (typeof window !== "undefined" && window.OneSignal) {
window.OneSignal.Slidedown.promptPush();
}
} catch (error) {
console.error("Gagal menyimpan data:", error);
setIsFollowing(false);
}
};
const handleFollowClick = () => {
if (!isLoggedIn) {
const confirmLogin = window.confirm(
"Anda belum bisa follow sebelum login.\n\nKlik OK untuk melanjutkan login dengan Google."
);
if (confirmLogin) {
localStorage.setItem("pending_follow", "true");
signIn('google');
}
return;
}
if (!isFollowing) {
eksekusiFollow(session?.user);
}
};
return (
{isFollowing ? (
<>Mengikuti
) : (
<>Ikuti Platform
)}
);
}
Langkah 5: Rute API Database (Follow)
Buat atau perbarui file app/api/follow/route.ts untuk menampung interaksi user. Penggunaan ON CONFLICT DO UPDATE memastikan keandalan saat data pengguna yang sama menimpa profilnya dari perangkat yang berbeda.
import { NextResponse } from 'next/server';
import { getRequestContext } from '@cloudflare/next-on-pages';
export const runtime = 'edge';
export async function POST(request: Request) {
try {
const { email, name, avatar, push_sub } = await request.json();
const env = getRequestContext().env as any;
await env.DB.prepare(
"INSERT INTO followers (email, name, avatar, push_sub) VALUES (?, ?, ?, ?) ON CONFLICT(email) DO UPDATE SET push_sub = excluded.push_sub"
).bind(email, name, avatar, push_sub).run();
return NextResponse.json({ success: true }, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Langkah 6: API Broadcast Berstandar Edge Runtime
Ini adalah jantung integrasi kita. Karena Cloudflare Pages mewajibkan export const runtime = 'edge'; pada setiap API Next.js, kita menggunakan REST API fetch murni untuk berkomunikasi dengan server OneSignal, menghindari bug kriptografi dari modul bawaan Node.js.
Buat file baru di app/api/notify/route.ts:
import { NextResponse } from 'next/server';
import { getRequestContext } from '@cloudflare/next-on-pages';
// Deklarasi mutlak Edge Runtime agar lolos proses Build Cloudflare
export const runtime = 'edge';
export async function POST(request: Request) {
try {
const env = getRequestContext().env as any;
const ONE_SIGNAL_APP_ID = env.ONESIGNAL_APP_ID;
const ONE_SIGNAL_REST_KEY = env.ONESIGNAL_REST_KEY;
if (!ONE_SIGNAL_APP_ID || !ONE_SIGNAL_REST_KEY) {
return NextResponse.json({ error: "OneSignal API Keys belum diatur di Cloudflare!" }, { status: 500 });
}
const body = await request.json();
// Komunikasi super cepat dari Edge ke API OneSignal
const response = await fetch('https://onesignal.com/api/v1/notifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Basic ${ONE_SIGNAL_REST_KEY}`
},
body: JSON.stringify({
app_id: ONE_SIGNAL_APP_ID,
included_segments: ['Subscribed Users'], // Target ke semua pengikut (Subscribers)
headings: { en: body.title || "Pembaruan Artikel Terbaru" },
contents: { en: body.body || "Ada pembaruan konten terbaru yang sayang untuk dilewatkan." },
url: body.url || "https://domainanda.com"
})
});
const data = await response.json();
return NextResponse.json({ success: true, message: "Notifikasi berhasil disebar via Edge!", data }, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: "Gagal Broadcast: " + error.message }, { status: 500 });
}
}
Langkah 7: Konfigurasi Keamanan (Environment Variables) di Cloudflare
Tahap final agar API Anda dapat bekerja di mode Production adalah menyuntikkan ID dan Kunci OneSignal secara rahasia langsung dari Dasbor Cloudflare.
- Masuk ke dasbor aplikasi Cloudflare Pages Anda, lalu navigasikan ke Settings -> Variables and Secrets.
- Tambahkan variabel pertama. Pilih tipe Text, beri nama
ONESIGNAL_APP_ID, dan tempelkan App ID Anda. - Tambahkan variabel kedua. Ubah tipenya menjadi "Secret" agar terenkripsi permanen. Beri nama
ONESIGNAL_REST_KEYdan masukkan string panjang REST API Key Anda. - Simpan pengaturan, lalu dorong kode Anda ke GitHub (
git push) untuk memicu deployment otomatis.
Kesimpulan dan Pengujian (Success Build)
Berkat pelepasan modul kriptografi Node.js (web-push) dan beralih ke protokol REST fetch, peringatan "Failed to produce a Cloudflare Pages build" yang kerap menghantui pengembang kini teratasi secara elegan. Kode akan memicu status hijau Success pada log mesin Cloudflare.
Untuk mengujinya secara live, Anda cukup membuka antarmuka console peramban dan mengirimkan payload fetch POST sederhana ke rute /api/notify. Dalam hitungan kurang dari dua detik, notifikasi cantik berdesain native sistem operasi Anda akan muncul menembus layar. Inilah wujud nyata dari arsitektur aplikasi web modern (JAMstack) yang matang, kencang, dan berskala industri.ur aplikasi web modern (JAMstack) yang matang, kencang, dan berskala industri.atau perbarui file app/api/follow/route.ts untuk menampung interaksi user. Penggunaan ON CONFLICT DO UPDATE memastikan keandalan saat data pengguna yang sama menimpa profilnya dari perangkat yang berbeda.
import { NextResponse } from 'next/server';
import { getRequestContext } from '@cloudflare/next-on-pages';
export const runtime = 'edge';
export async function POST(request: Request) {
try {
const { email, name, avatar, push_sub } = await request.json();
const env = getRequestContext().env as any;
await env.DB.prepare(
"INSERT INTO followers (email, name, avatar, push_sub) VALUES (?, ?, ?, ?) ON CONFLICT(email) DO UPDATE SET push_sub = excluded.push_sub"
).bind(email, name, avatar, push_sub).run();
return NextResponse.json({ success: true }, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Langkah 6: API Broadcast Berstandar Edge Runtime
Ini adalah jantung integrasi kita. Karena Cloudflare Pages mewajibkan export const runtime = 'edge'; pada setiap API Next.js, kita menggunakan REST API fetch murni untuk berkomunikasi dengan server OneSignal, menghindari bug kriptografi dari modul bawaan Node.js.
Buat file baru di app/api/notify/route.ts:
import { NextResponse } from 'next/server';
import { getRequestContext } from '@cloudflare/next-on-pages';
// Deklarasi mutlak Edge Runtime agar lolos proses Build Cloudflare
export const runtime = 'edge';
export async function POST(request: Request) {
try {
const env = getRequestContext().env as any;
const ONE_SIGNAL_APP_ID = env.ONESIGNAL_APP_ID;
const ONE_SIGNAL_REST_KEY = env.ONESIGNAL_REST_KEY;
if (!ONE_SIGNAL_APP_ID || !ONE_SIGNAL_REST_KEY) {
return NextResponse.json({ error: "OneSignal API Keys belum diatur di Cloudflare!" }, { status: 500 });
}
const body = await request.json();
// Komunikasi super cepat dari Edge ke API OneSignal
const response = await fetch('https://onesignal.com/api/v1/notifications', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Basic ${ONE_SIGNAL_REST_KEY}`
},
body: JSON.stringify({
app_id: ONE_SIGNAL_APP_ID,
included_segments: ['Subscribed Users'], // Target ke semua pengikut (Subscribers)
headings: { en: body.title || "Pembaruan Artikel Terbaru" },
contents: { en: body.body || "Ada pembaruan konten terbaru yang sayang untuk dilewatkan." },
url: body.url || "https://domainanda.com"
})
});
const data = await response.json();
return NextResponse.json({ success: true, message: "Notifikasi berhasil disebar via Edge!", data }, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: "Gagal Broadcast: " + error.message }, { status: 500 });
}
}
Langkah 7: Konfigurasi Keamanan (Environment Variables) di Cloudflare
Tahap final agar API Anda dapat bekerja di mode Production adalah menyuntikkan ID dan Kunci OneSignal secara rahasia langsung dari Dasbor Cloudflare.
- Masuk ke dasbor aplikasi Cloudflare Pages Anda, lalu navigasikan ke Settings -> Variables and Secrets.
- Tambahkan variabel pertama. Pilih tipe Text, beri nama
ONESIGNAL_APP_ID, dan tempelkan App ID Anda. - Tambahkan variabel kedua. Ubah tipenya menjadi "Secret" agar terenkripsi permanen. Beri nama
ONESIGNAL_REST_KEYdan masukkan string panjang REST API Key Anda. - Simpan pengaturan, lalu dorong kode Anda ke GitHub (
git push) untuk memicu deployment otomatis.
Kesimpulan dan Pengujian (Success Build)
Berkat pelepasan modul kriptografi Node.js (web-push) dan beralih ke protokol REST fetch, peringatan "Failed to produce a Cloudflare Pages build" yang kerap menghantui pengembang kini teratasi secara elegan. Kode akan memicu status hijau Success pada log mesin Cloudflare.
Untuk mengujinya secara live, Anda cukup membuka antarmuka console peramban dan mengirimkan payload fetch POST sederhana ke rute /api/notify. Dalam hitungan kurang dari dua detik, notifikasi cantik berdesain native sistem operasi Anda akan muncul menembus layar. Inilah wujud nyata dari arsitektur aplikasi web modern (JAMstack) yang matang, kencang, dan berskala industri.ur aplikasi web modern (JAMstack) yang matang, kencang, dan berskala industri.


