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

PE
Pena Edukasi
Ilustrasi arsitektur Web Push Notification modern menggunakan Next.js, Cloudflare Edge, dan OneSignal pada layar pengembang

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 fetch REST 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:

  1. App ID: Identitas publik aplikasi Anda.
  2. 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

Baca Juga: Adab kepada Orang Tua dan Guru: Dua Pintu Keberkahan dalam Hidup

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 });
  }
}

Baca Juga: Cara Generate dan Verifikasi API Key IndexNow di Bing Webmaster Tools

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.

  1. Masuk ke dasbor aplikasi Cloudflare Pages Anda, lalu navigasikan ke Settings -> Variables and Secrets.
  2. Tambahkan variabel pertama. Pilih tipe Text, beri nama ONESIGNAL_APP_ID, dan tempelkan App ID Anda.
  3. Tambahkan variabel kedua. Ubah tipenya menjadi "Secret" agar terenkripsi permanen. Beri nama ONESIGNAL_REST_KEY dan masukkan string panjang REST API Key Anda.
  4. 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 });
  }
}

Baca Juga: Cara Otomatisasi IndexNow Bing di Next.js dan Cloudflare Pages CMS

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.

  1. Masuk ke dasbor aplikasi Cloudflare Pages Anda, lalu navigasikan ke Settings -> Variables and Secrets.
  2. Tambahkan variabel pertama. Pilih tipe Text, beri nama ONESIGNAL_APP_ID, dan tempelkan App ID Anda.
  3. Tambahkan variabel kedua. Ubah tipenya menjadi "Secret" agar terenkripsi permanen. Beri nama ONESIGNAL_REST_KEY dan masukkan string panjang REST API Key Anda.
  4. 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.

Artikel Terkait