<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Raphi</title>
    <description>The latest articles on DEV Community by David Raphi (@draphy).</description>
    <link>https://dev.to/draphy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3076702%2F1f42d62e-d720-4b3b-bd03-7c48a934aa04.png</url>
      <title>DEV Community: David Raphi</title>
      <link>https://dev.to/draphy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/draphy"/>
    <language>en</language>
    <item>
      <title>How to Host a Personal Website for Free in 10 Minutes (Notion + Cloudflare)</title>
      <dc:creator>David Raphi</dc:creator>
      <pubDate>Wed, 28 Oct 2026 03:22:31 +0000</pubDate>
      <link>https://dev.to/draphy/how-to-host-a-personal-website-for-free-in-10-minutes-notion-cloudflare-29ka</link>
      <guid>https://dev.to/draphy/how-to-host-a-personal-website-for-free-in-10-minutes-notion-cloudflare-29ka</guid>
      <description>&lt;p&gt;Most personal website tutorials start with a framework. This one starts with a page you have already written, and it shows you how to host a personal website for free without adding another CMS.&lt;/p&gt;

&lt;p&gt;If your CV, project notes, publication list, or lab journal already lives in Notion, you do not need another CMS. You need a way to put that page on your own domain, make it indexable, and then stop thinking about it.&lt;/p&gt;

&lt;p&gt;That is the narrow problem Nooxy solves. It is an open-source Notion reverse proxy that runs on Cloudflare Workers. You point it at your published Notion pages, and it serves them on your domain with clean URLs, canonical tags, a sitemap, and your own CSS and JavaScript.&lt;/p&gt;

&lt;p&gt;Using Notion as a CMS keeps the writing workflow you already have. The free personal website hosting comes from Cloudflare Workers. Adding a Notion custom domain is optional. You can see the result on &lt;a href="https://draphy.org" rel="noopener noreferrer"&gt;draphy.org&lt;/a&gt;, &lt;a href="https://os.draphy.org" rel="noopener noreferrer"&gt;os.draphy.org&lt;/a&gt;, and &lt;a href="https://em-ucd.com" rel="noopener noreferrer"&gt;em-ucd.com&lt;/a&gt;. One Nooxy user also built a fan-made &lt;a href="https://expedition-33.pablogpz.me/music-wiki" rel="noopener noreferrer"&gt;Expedition 33 music wiki&lt;/a&gt; that documents 196 songs across four albums. It is available in English and Spanish.&lt;/p&gt;

&lt;p&gt;This guide goes from an empty terminal to a live site. The deploy itself takes about 10 minutes. Hosting and TLS cost $0 on Cloudflare's free plan. The only optional cost is the domain name, usually $10 to $15 per year. You can start on a free &lt;code&gt;*.workers.dev&lt;/code&gt; URL if you do not have one yet.&lt;/p&gt;

&lt;p&gt;I am the maintainer of Nooxy, so this comparison is not neutral. I have tried to make it fair and specific, with the trade-offs marked clearly so you can decide if it fits. Nooxy is not affiliated with Notion or Cloudflare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last checked: October 2026. Nooxy v2.0.0.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The short version&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What you get:&lt;/strong&gt; A personal website on your domain, with content managed in Notion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack:&lt;/strong&gt; Notion for content, Nooxy for the proxy and SEO, Cloudflare Workers for hosting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; About 10 to 15 minutes if your domain is already on Cloudflare. First-time DNS can take longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; $0 for hosting, TLS, and Notion's free plan. A custom domain is roughly $10 to $15 per year. A &lt;code&gt;*.workers.dev&lt;/code&gt; URL is free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coding:&lt;/strong&gt; None for the setup. You copy commands and edit one config file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; PhD students, researchers, writers, consultants, and developers who already write in Notion and want a low-maintenance site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a fit for:&lt;/strong&gt; E-commerce, logged-in memberships, or a completely terminal-free setup. The comparison below shows the closest alternatives.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Jump to:&lt;/strong&gt; The 10-minute setup · SEO checklist · Troubleshooting · The main trade-offs · FAQ&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I wanted a personal site at &lt;a href="https://draphy.org" rel="noopener noreferrer"&gt;draphy.org&lt;/a&gt;. I already documented everything in Notion, so paying another $20 a month for a CMS felt backwards.&lt;/p&gt;

&lt;p&gt;I tried Fruition first. It was the free option most people recommended at the time, but its maintenance had slowed and I wanted something I could adjust myself. I tried a few smaller proxies too, but they were harder to verify.&lt;/p&gt;

&lt;p&gt;So I wrote the small piece I actually needed: a proxy that sits between Notion and the browser, fixes the SEO plumbing, and lets me inject my own styles and scripts. No database. No build pipeline for content. No runtime dependencies. No monthly bill.&lt;/p&gt;

&lt;p&gt;I have run Nooxy on my own sites since 2025. It is a focused open-source tool, maintained by me, with a test suite and a habit of fixing what breaks when Notion changes. That is the honest version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually need
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Why you need it&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 22 or newer&lt;/td&gt;
&lt;td&gt;Runs the Nooxy CLI and Wrangler&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A Cloudflare account&lt;/td&gt;
&lt;td&gt;Hosts the Worker and manages DNS&lt;/td&gt;
&lt;td&gt;Free, no credit card for Workers Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;At least one published Notion page&lt;/td&gt;
&lt;td&gt;Your content&lt;/td&gt;
&lt;td&gt;Free plan is enough for a solo site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A domain name (optional)&lt;/td&gt;
&lt;td&gt;A professional URL&lt;/td&gt;
&lt;td&gt;Roughly $10 to $15 per year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 to 15 minutes&lt;/td&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Your time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check your Node version first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints &lt;code&gt;v22&lt;/code&gt; or higher, you are ready. If not, install the current LTS from &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;nodejs.org&lt;/a&gt;. Nooxy requires Node 22, so an old global Node install is the most common reason the CLI fails.&lt;/p&gt;

&lt;p&gt;You do not need to know Git, React, or how Cloudflare Workers work. You do need to be comfortable pasting commands into a terminal. That is the one real barrier, and I will not pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Notion, Nooxy, and Cloudflare fit together
&lt;/h2&gt;

&lt;p&gt;The architecture is intentionally boring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor -&amp;gt; yourdomain.com
              |
              v
     Cloudflare Worker (Nooxy)
              |  fetch the published page
              v
     your-workspace.notion.site
              |
              v
     HTML with your domain, SEO tags, CSS, and JS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notion is the CMS. You write and edit there. Published changes show up on the site without a rebuild.&lt;/p&gt;

&lt;p&gt;Nooxy is the translation layer. It maps a clean path like &lt;code&gt;/about&lt;/code&gt; to a Notion page ID, fetches the public page, and rewrites the response. It removes Notion's &lt;code&gt;noindex&lt;/code&gt; tag when indexing is enabled, adds a canonical URL, injects Open Graph and Twitter tags, adds a WebPage JSON-LD block with a nested WebSite, and serves &lt;code&gt;/sitemap.xml&lt;/code&gt; and &lt;code&gt;/robots.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers is the host. The Worker runs on Cloudflare's edge network. The free plan includes 100,000 requests per day, which is far more than a personal site usually needs. You can check the current limits in &lt;a href="https://developers.cloudflare.com/workers/platform/limits/" rel="noopener noreferrer"&gt;Cloudflare's docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cloudflare also has a cache that can sit in front of the Worker. It is off by default. Turn it on and a cached page is returned without running the Worker or fetching Notion. The setup below keeps it off; the Optional extras section shows the change if you want it.&lt;/p&gt;

&lt;p&gt;There is no Notion API key, no integration token, and no database sync. Nooxy serves the published page itself. That also means it keeps working with Notion database views, filters, and live updates, because it is not converting your page to static HTML.&lt;/p&gt;

&lt;p&gt;The trade-off is equally simple: this is a live proxy, not a static site generator. By default, every request fetches from Notion and rewrites the HTML, so the live page shares Notion's availability. That is what keeps edits live and databases interactive. If you need a site that always stays up independently of Notion, a static generator is the better fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "free" actually means
&lt;/h2&gt;

&lt;p&gt;A custom domain is not free, and I do not want anyone to start this guide thinking otherwise.&lt;/p&gt;

&lt;p&gt;Here is the real cost picture for this stack, checked in October 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;100,000 requests per day on the free plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS certificate&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Created and renewed by Cloudflare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;The free plan is enough for a solo personal site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nooxy&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;MIT licensed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom domain&lt;/td&gt;
&lt;td&gt;Roughly $10 to $15 per year&lt;/td&gt;
&lt;td&gt;Optional. The &lt;code&gt;*.workers.dev&lt;/code&gt; URL is free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is the whole cost picture for this tutorial. The comparison near the end covers Notion Sites, Super.so, Fruition, and a custom static site, including what each one charges. Prices change, so check the current pages before you buy.&lt;/p&gt;

&lt;p&gt;If $10 a year is still too much, launch on the free &lt;code&gt;*.workers.dev&lt;/code&gt; URL first. You can add the domain later without rebuilding anything.&lt;/p&gt;

&lt;p&gt;Cloudflare's Workers Cache has no separate price. A cache hit skips the Worker's CPU time, but it still counts as a request against the daily limit. The Optional extras section has the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute setup
&lt;/h2&gt;

&lt;p&gt;You will do 11 steps. Steps 1 through 8 get you a live site on a &lt;code&gt;workers.dev&lt;/code&gt; URL. Step 9 adds your domain. Steps 10 and 11 add pages and styling. The 10 minutes is hands-on time, not reading time, so read the steps once before you start.&lt;/p&gt;

&lt;p&gt;If you get stuck, the &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/examples/cloudflare/README.md" rel="noopener noreferrer"&gt;full Cloudflare deployment guide&lt;/a&gt; has the same steps with more detail and troubleshooting. The Nooxy documentation links in this article are pinned to the commit this tutorial was written against, so they will not break when the README changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Cloudflare project
&lt;/h3&gt;

&lt;p&gt;In an empty folder you want to work in, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cloudflare@latest my-nooxy-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be asked a few questions. Choose these options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;What to select&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What would you like to start with?&lt;/td&gt;
&lt;td&gt;Hello World example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which template?&lt;/td&gt;
&lt;td&gt;Hello World Worker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which language?&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do you want to use git?&lt;/td&gt;
&lt;td&gt;Your choice. Yes is fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do you want to deploy?&lt;/td&gt;
&lt;td&gt;No, we will do it later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact wording may change as Cloudflare updates its CLI. The important part is a plain Worker with TypeScript. Do not choose a static site or Pages template. Nooxy needs a Worker because it proxies requests.&lt;/p&gt;

&lt;p&gt;Move into the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-nooxy-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you should have &lt;code&gt;src/index.ts&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, and &lt;code&gt;wrangler.jsonc&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Install Nooxy and create the config
&lt;/h3&gt;

&lt;p&gt;Run these inside &lt;code&gt;my-nooxy-site&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nooxy
npx nooxy init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npx nooxy init&lt;/code&gt; creates a &lt;code&gt;nooxy/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-nooxy-site/
├── src/
│   └── index.ts
├── nooxy/
│   ├── config.js
│   ├── head.css
│   ├── head.js
│   ├── body.js
│   └── header.html
├── package.json
└── wrangler.jsonc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four optional files are where your custom CSS, JavaScript, and header HTML go. You can leave them empty for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Publish your Notion pages and copy the IDs
&lt;/h3&gt;

&lt;p&gt;Every Notion page has a 32-character page ID. Nooxy uses that ID to connect a URL path to a page.&lt;/p&gt;

&lt;p&gt;For each page you want on your site:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the page in Notion.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Share&lt;/strong&gt; in the top right.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Publish&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Publish to web&lt;/strong&gt; or click &lt;strong&gt;Publish&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Copy the public link.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The published link looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-workspace.notion.site/Your-Page-Title-abc123def456789012345678901234ab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The page ID is the 32 hex characters at the end of the URL path, after the last hyphen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;abc123def456789012345678901234ab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ignore anything after a &lt;code&gt;?&lt;/code&gt;, such as &lt;code&gt;?pvs=4&lt;/code&gt;. That is a tracking parameter, not part of the ID.&lt;/p&gt;

&lt;p&gt;The hostname, &lt;code&gt;your-workspace.notion.site&lt;/code&gt;, is your &lt;code&gt;notionDomain&lt;/code&gt;. You will need both the ID and the hostname in the next step.&lt;/p&gt;

&lt;p&gt;A few details that save time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page ID contains only letters &lt;code&gt;a&lt;/code&gt; through &lt;code&gt;f&lt;/code&gt; and numbers &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;9&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If your link shows a UUID with hyphens, remove the hyphens. Nooxy expects 32 characters with no separators.&lt;/li&gt;
&lt;li&gt;The page must be published as a Notion Site. The general "Anyone on the web with link" setting is not the same thing.&lt;/li&gt;
&lt;li&gt;The page ID is an identifier, not a password. It does not grant edit access, but it does point at a public page.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Check your subpages before you publish.&lt;/strong&gt; Notion publishes a page's subpages by default. If you have private notes, drafts, or anything you would not put on the public internet under that page, restrict the subpage permissions first. Notion's own help page also notes that published page metadata can include contributor names, profile photos, and email addresses. Review the page before you make it public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notion explains the publishing flow in &lt;a href="https://www.notion.com/help/public-pages-and-web-publishing" rel="noopener noreferrer"&gt;Publish a Notion Site&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure the site
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;nooxy/config.js&lt;/code&gt;. Do not replace the whole file. Edit the matching values in place, and leave the imports and the generated &lt;code&gt;custom*&lt;/code&gt; fields at the bottom alone.&lt;/p&gt;

&lt;p&gt;The four required values look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Your site's hostname, without https://&lt;/span&gt;
  &lt;span class="c1"&gt;// You can start with your workers.dev URL and update it after the first deploy.&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-nooxy-site.YOUR-SUBDOMAIN.workers.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// The hostname of your published Notion site&lt;/span&gt;
  &lt;span class="na"&gt;notionDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-workspace.notion.site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Your name or site name&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// URL path -&amp;gt; 32-character Notion page ID&lt;/span&gt;
  &lt;span class="na"&gt;slugToPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_HOME_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ABOUT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECTS_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Leave the rest of the file as it is.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your actual file also has &lt;code&gt;seo&lt;/code&gt;, &lt;code&gt;pageMetadata&lt;/code&gt;, &lt;code&gt;fof&lt;/code&gt;, &lt;code&gt;subDomains&lt;/code&gt;, and other options. They are commented out by default. You can uncomment the ones you need later. The &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/README.md#configuration-reference" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt; lists every field.&lt;/p&gt;

&lt;p&gt;For a basic site, add this &lt;code&gt;seo&lt;/code&gt; block inside &lt;code&gt;SITE_CONFIG&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;seo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;indexing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;defaultAuthor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;brandReplacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;indexing: true&lt;/code&gt; is the default, but setting it explicitly makes the intent clear. It tells Nooxy to remove Notion's &lt;code&gt;noindex&lt;/code&gt; tag and add canonical and robots tags. &lt;code&gt;brandReplacement&lt;/code&gt; replaces "Notion" in meta tags with your name or brand.&lt;/p&gt;

&lt;p&gt;If you do not know your &lt;code&gt;workers.dev&lt;/code&gt; subdomain yet, leave the placeholder. Deploy once in Step 8, copy the real URL, update &lt;code&gt;domain&lt;/code&gt;, and redeploy. The site works either way. Only the canonical URLs are wrong until you fix it.&lt;/p&gt;

&lt;p&gt;One common mistake: &lt;code&gt;domain&lt;/code&gt; is a hostname, not a full URL. Use &lt;code&gt;example.com&lt;/code&gt;, not &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Generate the injected assets
&lt;/h3&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads &lt;code&gt;head.css&lt;/code&gt;, &lt;code&gt;head.js&lt;/code&gt;, &lt;code&gt;body.js&lt;/code&gt;, and &lt;code&gt;header.html&lt;/code&gt;, minifies them, and writes the generated strings that &lt;code&gt;config.js&lt;/code&gt; imports. It creates &lt;code&gt;nooxy/generated/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run this command every time you change anything in the &lt;code&gt;nooxy/&lt;/code&gt; folder. That is the rule that prevents the "why is my CSS not showing up?" problem later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Connect Nooxy to the Worker
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;src/index.ts&lt;/code&gt; and replace everything in it with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initializeNooxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nooxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../nooxy/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file. That is the entire integration. &lt;code&gt;initializeNooxy&lt;/code&gt; returns a function that takes a standard &lt;code&gt;Request&lt;/code&gt; and returns a standard &lt;code&gt;Response&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Test locally
&lt;/h3&gt;

&lt;p&gt;Start the local Worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8787" rel="noopener noreferrer"&gt;http://localhost:8787&lt;/a&gt;. You should see your Notion page, served by Nooxy.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;Ctrl + C&lt;/code&gt; when you are done.&lt;/p&gt;

&lt;p&gt;If it does not work, check these first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Page not found&lt;/td&gt;
&lt;td&gt;Is the page published? Is the page ID exactly 32 characters? Does &lt;code&gt;notionDomain&lt;/code&gt; match the published hostname?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blank page&lt;/td&gt;
&lt;td&gt;The page ID is usually wrong. Copy it again from the published URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cannot find module 'nooxy'&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;npm install nooxy&lt;/code&gt; in the project root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port already in use&lt;/td&gt;
&lt;td&gt;Stop the other process, or let Wrangler pick another port&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nooxy automatically uses &lt;code&gt;localhost&lt;/code&gt; for canonical URLs during local development, so you do not need to change &lt;code&gt;domain&lt;/code&gt; just to test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Deploy
&lt;/h3&gt;

&lt;p&gt;Log in to Cloudflare once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a browser window. Click &lt;strong&gt;Allow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it finishes, Wrangler prints a URL like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://my-nooxy-site.your-subdomain.workers.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open it. Your Notion page is now live on the internet.&lt;/p&gt;

&lt;p&gt;If that URL does not match the &lt;code&gt;domain&lt;/code&gt; in &lt;code&gt;nooxy/config.js&lt;/code&gt;, update &lt;code&gt;domain&lt;/code&gt;, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a working site. The rest is making it yours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Put it on your own domain
&lt;/h3&gt;

&lt;p&gt;This is the part that makes the site feel real. It is also the only part that can cost money.&lt;/p&gt;

&lt;p&gt;If you do not have a domain yet, buy one from &lt;a href="https://dash.cloudflare.com/?to=/:account/domains/register" rel="noopener noreferrer"&gt;Cloudflare Registrar&lt;/a&gt;, &lt;a href="https://www.namecheap.com" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt;, or &lt;a href="https://porkbun.com" rel="noopener noreferrer"&gt;Porkbun&lt;/a&gt;. A &lt;code&gt;.com&lt;/code&gt; is usually in the $10 to $15 per year range. Prices vary by extension. Do not buy hosting. You do not need it.&lt;/p&gt;

&lt;h4&gt;
  
  
  If your domain is already on Cloudflare
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://dash.cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare Dashboard&lt;/a&gt; and open &lt;strong&gt;Workers &amp;amp; Pages&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select your Worker.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Domains &amp;amp; Routes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add&lt;/strong&gt; &amp;gt; &lt;strong&gt;Custom Domain&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter your domain, for example &lt;code&gt;example.com&lt;/code&gt;, and confirm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cloudflare creates the DNS record and the TLS certificate for you. You do not need to add a CNAME manually. In fact, a Custom Domain cannot be created if the hostname already has a CNAME record, so delete any old record for that hostname first.&lt;/p&gt;

&lt;h4&gt;
  
  
  If your domain is not on Cloudflare yet
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;In the Cloudflare dashboard, go to &lt;strong&gt;Websites&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add a Site&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter your domain and choose the &lt;strong&gt;Free&lt;/strong&gt; plan.&lt;/li&gt;
&lt;li&gt;Cloudflare gives you two nameservers.&lt;/li&gt;
&lt;li&gt;At your registrar, replace the current nameservers with Cloudflare's.&lt;/li&gt;
&lt;li&gt;Wait until the domain shows &lt;strong&gt;Active&lt;/strong&gt;. This is usually under an hour, but it can take up to 24 hours.&lt;/li&gt;
&lt;li&gt;Then follow the Custom Domain steps above.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Update the config
&lt;/h4&gt;

&lt;p&gt;Once the domain is attached, change &lt;code&gt;domain&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Make &lt;code&gt;www&lt;/code&gt; work
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;example.com&lt;/code&gt; and &lt;code&gt;www.example.com&lt;/code&gt; are different hostnames. Adding only one will not make the other work.&lt;/p&gt;

&lt;p&gt;For SEO, pick one canonical version and redirect the other. The usual choice is &lt;code&gt;www&lt;/code&gt; to apex:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;www.example.com&lt;/code&gt; as a second Custom Domain on the same Worker.&lt;/li&gt;
&lt;li&gt;Add this to &lt;code&gt;nooxy/config.js&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;subDomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;www&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now &lt;code&gt;https://www.example.com&lt;/code&gt; returns a 301 redirect to &lt;code&gt;https://example.com&lt;/code&gt;. All traffic and link equity point at one hostname.&lt;/p&gt;

&lt;p&gt;If you prefer the opposite direction, reverse the values. Do not serve the same content on both hostnames without a redirect. That is duplicate content, and it splits whatever search authority you earn.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developers.cloudflare.com/workers/configuration/routing/custom-domains/" rel="noopener noreferrer"&gt;Cloudflare custom domain docs&lt;/a&gt; confirm the important detail: Custom Domains require an exact hostname match, and Cloudflare creates the DNS record and certificate for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Add more pages
&lt;/h3&gt;

&lt;p&gt;To add a page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create it in Notion.&lt;/li&gt;
&lt;li&gt;Publish it.&lt;/li&gt;
&lt;li&gt;Copy the 32-character page ID.&lt;/li&gt;
&lt;li&gt;Add a path and ID to &lt;code&gt;slugToPage&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;slugToPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_HOME_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ABOUT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECTS_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_CONTACT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/docs/getting-started&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_DOCS_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nested paths work. The sitemap is generated from this map, so any page you want search engines to discover should be in &lt;code&gt;slugToPage&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 11: Make it yours
&lt;/h3&gt;

&lt;p&gt;Nooxy gives you four injection points. They are all optional.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nooxy/head.css&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom CSS&lt;/td&gt;
&lt;td&gt;Injected into &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nooxy/head.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Early JavaScript&lt;/td&gt;
&lt;td&gt;Before page content loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nooxy/body.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Main JavaScript&lt;/td&gt;
&lt;td&gt;Before &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nooxy/header.html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom header HTML&lt;/td&gt;
&lt;td&gt;At the top of the page body&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A small CSS starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* nooxy/head.css */&lt;/span&gt;
&lt;span class="nc"&gt;.notion-topbar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.notion-page-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;900px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- nooxy/header.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"site-nav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/projects"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Projects&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/contact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After any change in &lt;code&gt;nooxy/&lt;/code&gt;, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail from the docs: Nooxy injects its own stylesheet after yours, so a rule with the same specificity can lose even if it appears first. If you need to override Nooxy's own styles, use a more specific selector as well as &lt;code&gt;!important&lt;/code&gt;. The &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/README.md#project-files" rel="noopener noreferrer"&gt;project files documentation&lt;/a&gt; has examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional extras you may want later
&lt;/h2&gt;

&lt;p&gt;You do not need any of these to launch. The table covers the four settings people ask about most often. All but the last live in &lt;code&gt;nooxy/config.js&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fof: { page: 'YOUR_404_PAGE_ID', slug: '/404' }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sends unknown paths to a published Notion page instead of a plain 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;googleFont: 'Inter'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loads a Google Font site-wide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nooxy: { showBadge: false }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hides the small "Made with Nooxy" badge, which is shown by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lets one Worker serve several sites, each with its own config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One SEO caveat on &lt;code&gt;fof&lt;/code&gt;: it redirects unknown paths with a 301, so the custom page returns a 200 rather than a true 404. That is friendlier for visitors, but if accurate status codes matter to you, leave &lt;code&gt;fof&lt;/code&gt; off.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;configKey&lt;/code&gt; option is the only one that changes &lt;code&gt;src/index.ts&lt;/code&gt;. Instead of passing the config directly, pass an object with a stable key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site-a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE_A&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;site-b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE_B&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then choose the site by hostname in your &lt;code&gt;fetch&lt;/code&gt; handler. The &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/README.md#hosting-several-sites-from-one-deployment" rel="noopener noreferrer"&gt;README section on hosting several sites&lt;/a&gt; has the complete example. If you omit &lt;code&gt;configKey&lt;/code&gt;, Nooxy falls back to the site's &lt;code&gt;domain&lt;/code&gt;, but an explicit key is clearer and is required when two sites share a domain.&lt;/p&gt;

&lt;p&gt;As always, run &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt; after changing the config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Turn on Workers Cache
&lt;/h3&gt;

&lt;p&gt;Every uncached request runs the Worker and fetches the Notion page. That is what keeps edits instant, but it is also the slowest part of a page load. Cloudflare's Workers Cache stores the finished HTML at the edge and returns it without running the Worker at all. It is the right cache for a Worker. A normal Cache Rule will not do this, because the rule sits with the zone cache, not in front of the Worker.&lt;/p&gt;

&lt;p&gt;Two changes. First, turn it on in &lt;code&gt;wrangler.jsonc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"cache"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This needs Wrangler 4.69 or newer. The project you created in Step 1 already has a current Wrangler version.&lt;/p&gt;

&lt;p&gt;Second, give the HTML an edge TTL. Notion sends &lt;code&gt;cache-control: public, max-age=0, must-revalidate&lt;/code&gt;, and Nooxy passes that through. At &lt;code&gt;max-age=0&lt;/code&gt;, Cloudflare will not serve a cached copy. Wrap the response in &lt;code&gt;src/index.ts&lt;/code&gt; and set an edge-only header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initializeNooxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nooxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../nooxy/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloudflare-cdn-cache-control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public, max-age=300&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cloudflare-cdn-cache-control&lt;/code&gt; is read by Cloudflare and stripped before the response reaches the browser. The browser still revalidates on each visit, so visitors do not keep a stale page. The edge keeps the HTML for five minutes. Use 60 seconds if you edit often, or 3600 if you rarely change the page.&lt;/p&gt;

&lt;p&gt;The status check matters. Without it, a 503 during a Notion outage would also be cached for five minutes, which turns a short outage into a longer one.&lt;/p&gt;

&lt;p&gt;Deploy, then check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second response should include &lt;code&gt;cf-cache-status: HIT&lt;/code&gt;. The first is a miss while the cache fills.&lt;/p&gt;

&lt;p&gt;Two caveats. Edits can take up to the TTL to appear. A redeploy starts a fresh cache because the Worker version is part of the cache key, so deploy when you need a change out immediately. And do not turn this on if you use &lt;code&gt;configKey&lt;/code&gt; to serve several domains from one Worker: the cache key is the path, not the hostname, so two sites would share entries for the same path.&lt;/p&gt;

&lt;p&gt;If you would rather keep every request live, leave the cache off. The site works fine without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEO checklist after launch
&lt;/h2&gt;

&lt;p&gt;Nooxy handles the technical plumbing. The writing, links, and rankings are still yours to earn. Here is what to do after the site is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Check the three URLs
&lt;/h3&gt;

&lt;p&gt;Open these in a browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/robots.txt
https://example.com/sitemap.xml
https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; should point to your sitemap. &lt;code&gt;sitemap.xml&lt;/code&gt; should list every slug in &lt;code&gt;slugToPage&lt;/code&gt;. The homepage should load.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect the page source
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;View Page Source&lt;/strong&gt; (&lt;code&gt;Ctrl+U&lt;/code&gt; on Windows/Linux, &lt;code&gt;Cmd+U&lt;/code&gt; on Mac), not the rendered DOM in DevTools. Nooxy rewrites the HTML on the server, so the source is what crawlers see.&lt;/p&gt;

&lt;p&gt;Search for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;link rel="canonical"&lt;/code&gt; and confirm it points to your domain, not &lt;code&gt;notion.site&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;meta name="robots" content="index, follow&lt;/code&gt; and confirm it is present.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;meta name="description"&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; and confirm the WebPage schema and its nested WebSite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the canonical URL points to &lt;code&gt;notion.site&lt;/code&gt;, your &lt;code&gt;domain&lt;/code&gt; value is wrong. Fix it and redeploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add per-page metadata
&lt;/h3&gt;

&lt;p&gt;This is the highest-impact SEO change you can make. Notion's default titles and descriptions are generic, and they often include the word "Notion".&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;pageMetadata&lt;/code&gt; block to &lt;code&gt;nooxy/config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pageMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_HOME_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada Lovelace | Mathematician and Writer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notes, papers, and projects by Ada Lovelace.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/og/home.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada Lovelace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ABOUT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;About Ada Lovelace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A short biography, current work, and contact details.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada Lovelace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the page ID as the key. The &lt;code&gt;image&lt;/code&gt; should be an absolute URL, ideally 1200 by 630 pixels. Add your own social image at that URL.&lt;/p&gt;

&lt;p&gt;Then run &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nooxy also exposes &lt;code&gt;seo.keywords&lt;/code&gt; and &lt;code&gt;seo.aiAttribution&lt;/code&gt;. They are optional and do not replace a clear title, description, and page copy.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Submit the sitemap to search engines
&lt;/h3&gt;

&lt;p&gt;For Google:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://search.google.com/search-console/about" rel="noopener noreferrer"&gt;Google Search Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add a property. A Domain property is easiest if your DNS is on Cloudflare.&lt;/li&gt;
&lt;li&gt;Verify ownership. Cloudflare makes the DNS verification a copy-paste job.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Sitemaps&lt;/strong&gt; and submit &lt;code&gt;https://example.com/sitemap.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;URL Inspection&lt;/strong&gt; on your homepage and click &lt;strong&gt;Request indexing&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Bing, add the site in &lt;a href="https://www.bing.com/webmasters" rel="noopener noreferrer"&gt;Bing Webmaster Tools&lt;/a&gt; and submit the same sitemap. You can also import from Google Search Console.&lt;/p&gt;

&lt;p&gt;Do not expect instant results. Notion's own documentation says Notion Sites can take up to four weeks to be indexed. A custom domain with a submitted sitemap can be faster, but no one can promise a timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add analytics only if you will use it
&lt;/h3&gt;

&lt;p&gt;Nooxy supports a GA4 measurement ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;googleTagID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;G-XXXXXXXXXX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is optional. If you install analytics, use it to learn which pages people actually read. Do not install three tools and then ignore all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating your site later
&lt;/h2&gt;

&lt;p&gt;There are two loops, and mixing them up causes most of the confusion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you changed&lt;/th&gt;
&lt;th&gt;What to run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content in Notion&lt;/td&gt;
&lt;td&gt;Nothing. Publish the page and the site updates, within the cache TTL if you turned on Workers Cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;nooxy/config.js&lt;/code&gt;, &lt;code&gt;head.css&lt;/code&gt;, &lt;code&gt;head.js&lt;/code&gt;, &lt;code&gt;body.js&lt;/code&gt;, or &lt;code&gt;header.html&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx nooxy generate&lt;/code&gt; then &lt;code&gt;npm run deploy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain or DNS settings&lt;/td&gt;
&lt;td&gt;Update them in Cloudflare and the &lt;code&gt;domain&lt;/code&gt; field, then redeploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A new page&lt;/td&gt;
&lt;td&gt;Add it to &lt;code&gt;slugToPage&lt;/code&gt;, then &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Content changes are live because Nooxy fetches the published Notion page on each request. Config and asset changes need a redeploy because they are compiled into the Worker.&lt;/p&gt;

&lt;p&gt;With Workers Cache on, the edge can serve a copy for up to the TTL you set. Redeploying starts a fresh cache, because the Worker version is part of the cache key.&lt;/p&gt;

&lt;p&gt;That is the maintenance model: almost none for content, a package update every few months, and a redeploy when you change the design or a route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;404 or "Page not found"&lt;/td&gt;
&lt;td&gt;Page is not published, the ID is wrong, or &lt;code&gt;notionDomain&lt;/code&gt; is wrong&lt;/td&gt;
&lt;td&gt;Publish the page, copy the 32-character ID again, and use the &lt;code&gt;.notion.site&lt;/code&gt; hostname&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blank page&lt;/td&gt;
&lt;td&gt;Usually a wrong page ID&lt;/td&gt;
&lt;td&gt;Re-copy the ID from the published URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS is not applied&lt;/td&gt;
&lt;td&gt;You forgot to regenerate or redeploy&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO tags are missing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;seo.indexing&lt;/code&gt; is false, you are looking at the rendered DOM, or the Worker was not redeployed&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;indexing: true&lt;/code&gt;, view the page source, and redeploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edited a Notion page but the site shows the old copy&lt;/td&gt;
&lt;td&gt;Workers Cache is serving the cached HTML&lt;/td&gt;
&lt;td&gt;Wait for the TTL, or redeploy to start a fresh cache. A zone "Purge Everything" does not clear the Worker cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cannot find module 'nooxy'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nooxy is not installed in the project root&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;npm install nooxy&lt;/code&gt; inside &lt;code&gt;my-nooxy-site&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom domain does not work&lt;/td&gt;
&lt;td&gt;DNS is not active, an old record conflicts, or the domain is not attached to the Worker&lt;/td&gt;
&lt;td&gt;Wait for Active, remove the conflicting record, and add the Custom Domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;www&lt;/code&gt; does not work&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www&lt;/code&gt; is not attached, or there is no redirect&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;www&lt;/code&gt; as a Custom Domain and configure &lt;code&gt;subDomains&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local server will not start&lt;/td&gt;
&lt;td&gt;Wrong folder or port conflict&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cd&lt;/code&gt; into the project and stop the other process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pinned &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/README.md#troubleshooting" rel="noopener noreferrer"&gt;troubleshooting section&lt;/a&gt; and the &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/examples/cloudflare/README.md#troubleshooting" rel="noopener noreferrer"&gt;Cloudflare guide's troubleshooting section&lt;/a&gt; cover more cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main trade-offs
&lt;/h2&gt;

&lt;p&gt;Nooxy is built for public, content-first sites. It does a few things well instead of trying to be every CMS. Here is the short list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The setup is terminal-first, but it is short.&lt;/strong&gt; You paste the commands in this guide and edit one config file. After that, everyday updates happen in Notion. The payoff is full control over CSS, JavaScript, and SEO instead of a locked builder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content is served live from Notion.&lt;/strong&gt; That makes edits appear immediately and keeps database views interactive. Without caching, the site follows Notion's uptime: if Notion is unreachable, Nooxy returns a 503, not a stale copy. Workers Cache changes that trade: a cached page can survive a short outage, but edits can lag by the TTL. If you need a site that always stays up independently, a static export is still the better fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It publishes public pages, not accounts.&lt;/strong&gt; Nooxy does not bundle login, memberships, payments, or private content. Comments can be added with a third-party script. If your site needs accounts or a full application, pair Nooxy with another service or choose an app stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The domain is the only likely bill.&lt;/strong&gt; Hosting, TLS, and the Workers free tier are $0. A custom domain is roughly $10 to $15 per year, and a &lt;code&gt;*.workers.dev&lt;/code&gt; URL is free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nooxy is open source, MIT licensed, and supported through GitHub issues, with tests and CI in the repo. If those points fit your site, the rest of this guide is the fast path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nooxy vs Notion Sites, Super.so, and Fruition
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;What to know&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nooxy + Cloudflare Workers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0 hosting. Domain roughly $10 to $15 per year, or $0 on &lt;code&gt;*.workers.dev&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A free, live Notion site with full CSS, JS, and SEO control&lt;/td&gt;
&lt;td&gt;Short terminal setup. Optional edge cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Notion Sites&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free basic publishing. A Plus plan with the custom domain add-on is roughly $18 to $22 per month&lt;/td&gt;
&lt;td&gt;The fastest official, click-based option&lt;/td&gt;
&lt;td&gt;Custom domain and advanced SEO are paid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Super.so&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free on &lt;code&gt;super.site&lt;/code&gt;. Custom domain plans start at $16 per month&lt;/td&gt;
&lt;td&gt;A polished static Notion site&lt;/td&gt;
&lt;td&gt;Paid for a custom domain. Static export loses live database filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fruition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Existing legacy setups&lt;/td&gt;
&lt;td&gt;Maintenance has slowed; check recent activity before starting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom static site&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosting can be $0. Your time is the cost&lt;/td&gt;
&lt;td&gt;Full control and a custom build&lt;/td&gt;
&lt;td&gt;You own the build and maintenance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A fair summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nooxy&lt;/strong&gt; is a free, open-source option that keeps Notion live and gives you full design and SEO control. The trade-off is a short terminal setup. Content follows Notion uptime unless you turn on the optional edge cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion Sites&lt;/strong&gt; is the easiest official option. It is click-based and quick to launch; custom domains and advanced SEO are paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Super.so&lt;/strong&gt; is a polished static builder with a paid custom-domain plan. It is a good choice if you want a static export and do not need live database filtering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fruition&lt;/strong&gt; helped popularize this approach, but its maintenance has slowed. Check the recent activity before choosing it for a new site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A custom static site&lt;/strong&gt; gives you maximum control if you enjoy owning the build pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nooxy's sweet spot is free hosting, live Notion content, and full control, with a short setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Nooxy is for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nooxy is a strong fit if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already write in Notion and want one source of truth.&lt;/li&gt;
&lt;li&gt;You want a custom domain without a monthly hosting bill.&lt;/li&gt;
&lt;li&gt;You want full control over your CSS, JavaScript, and SEO tags.&lt;/li&gt;
&lt;li&gt;You want live Notion databases and filtering on your site.&lt;/li&gt;
&lt;li&gt;You are a PhD student, researcher, writer, consultant, or developer who needs a clean Notion portfolio website, lab site, or blog.&lt;/li&gt;
&lt;li&gt;You are comfortable copying a few terminal commands once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose another tool if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need a completely click-based setup and never want to open a terminal. Notion Sites is the closest fit.&lt;/li&gt;
&lt;li&gt;You need accounts, payments, private content, or a full web application. Nooxy is a publishing layer, not an app framework.&lt;/li&gt;
&lt;li&gt;You need a fully static site that stays online independently of Notion. A static export is the better fit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the second list sounds like your project, Notion Sites or Super.so are good choices for that job. Nooxy is the better fit when you want live Notion content, full control, and no monthly hosting bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Nooxy really free?
&lt;/h3&gt;

&lt;p&gt;Yes for hosting. Nooxy is MIT licensed, Cloudflare Workers has a free tier with 100,000 requests per day, and Notion's free plan is enough for a solo personal site. A custom domain is the only likely cost, usually $10 to $15 per year. The &lt;code&gt;*.workers.dev&lt;/code&gt; URL costs nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a Notion custom domain?
&lt;/h3&gt;

&lt;p&gt;No. You can launch on the free &lt;code&gt;*.workers.dev&lt;/code&gt; URL and add a domain later. A Notion custom domain looks more professional and helps with branding, but it is not required to get the site working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to know how to code?
&lt;/h3&gt;

&lt;p&gt;No for the setup. The guide gives you every command and one config file to edit. Customizing CSS or JavaScript is optional, and that is where code helps. The launch itself does not require writing code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Notion databases on my website?
&lt;/h3&gt;

&lt;p&gt;Yes. Nooxy serves the published Notion page, so database views and filtering that work on the public Notion page should work on your domain. It does not use the Notion API, and it does not convert the page to static HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do content changes appear automatically?
&lt;/h3&gt;

&lt;p&gt;Yes. Edit and publish the page in Notion, and the site reflects the change on the next request. If you turned on Workers Cache, the change appears within the cache TTL. Changes to files in &lt;code&gt;nooxy/&lt;/code&gt; are different: run &lt;code&gt;npx nooxy generate&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I turn on Cloudflare caching?
&lt;/h3&gt;

&lt;p&gt;Yes if you want faster loads and can wait a few minutes for an edit to appear. No if you serve several domains from one Worker with &lt;code&gt;configKey&lt;/code&gt;, because the cache key does not include the hostname. The Optional extras section has a five-minute setup that leaves browser caching alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a Nooxy site good for SEO?
&lt;/h3&gt;

&lt;p&gt;A Nooxy site serves your published Notion website on your own domain and gives you the technical pieces: no &lt;code&gt;noindex&lt;/code&gt;, canonical URLs, clean paths, per-page titles and descriptions, Open Graph and Twitter tags, a sitemap, robots.txt, and a WebPage JSON-LD block with a nested WebSite. Rankings still depend on your content, your competition, and your links. No tool can guarantee a position.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this different from Notion Sites and Super.so?
&lt;/h3&gt;

&lt;p&gt;Notion Sites is the official, click-based option. Custom domains and advanced SEO are paid. Super.so is a polished static site builder for Notion, also paid for a custom domain. Nooxy is free and open source, keeps live Notion content, and gives you full CSS, JS, and SEO control. The launch is terminal-based, and the guide walks through every command.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if Notion goes down?
&lt;/h3&gt;

&lt;p&gt;Without caching, Nooxy fetches the published page on each request, so your site follows Notion's uptime. That is the trade-off for live edits and interactive databases. If Notion is unreachable, Nooxy returns a 503 rather than a stale copy. With Workers Cache on, a recently cached page can survive a short outage, but edits can take up to the TTL to appear. If you need a fully static fallback, a static export is the better fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Cloudflare Pages instead of Workers?
&lt;/h3&gt;

&lt;p&gt;Not with this guide. Cloudflare Pages is built for static assets. Nooxy needs a Worker because it proxies requests and rewrites the HTML response. The guide uses Workers for that reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I move away later?
&lt;/h3&gt;

&lt;p&gt;Your content stays in Notion, and your domain stays yours. Export your Notion pages, point the domain at the new host, and update the &lt;code&gt;domain&lt;/code&gt; field if you keep Nooxy around. There is no proprietary database to migrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-minute checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Node.js 22 or newer installed&lt;/li&gt;
&lt;li&gt;[ ] Cloudflare account created&lt;/li&gt;
&lt;li&gt;[ ] Notion page published, with subpage permissions checked&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm create cloudflare@latest my-nooxy-site&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm install nooxy&lt;/code&gt; and &lt;code&gt;npx nooxy init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Page IDs and &lt;code&gt;notionDomain&lt;/code&gt; copied&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;nooxy/config.js&lt;/code&gt; edited&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npx nooxy generate&lt;/code&gt; run&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;src/index.ts&lt;/code&gt; replaced&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npm run dev&lt;/code&gt; tested at &lt;code&gt;localhost:8787&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;npx wrangler login&lt;/code&gt; and &lt;code&gt;npm run deploy&lt;/code&gt; completed&lt;/li&gt;
&lt;li&gt;[ ] Custom domain and &lt;code&gt;www&lt;/code&gt; redirect configured&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;pageMetadata&lt;/code&gt; added&lt;/li&gt;
&lt;li&gt;[ ] Sitemap submitted to Google Search Console&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Most people do not need a custom CMS. They need a place to write and a boring pipe to the web.&lt;/p&gt;

&lt;p&gt;If you already write in Notion, Nooxy is that pipe. It is not the right tool for every site, but for a public, content-first personal site it is a strong fit. If you want a site you can set up in about 10 minutes, host for $0, and update from Notion without touching a build pipeline, this is the fast path.&lt;/p&gt;

&lt;p&gt;If you try it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/draphy/nooxy" rel="noopener noreferrer"&gt;Star the repo&lt;/a&gt; if it saves you money or time.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/draphy/nooxy/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt; if something breaks. Include the error and the page ID format, never private content.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/draphy/nooxy/discussions" rel="noopener noreferrer"&gt;Share your site in Discussions&lt;/a&gt;. I genuinely like seeing what people build.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/examples/cloudflare/README.md" rel="noopener noreferrer"&gt;pinned Cloudflare guide&lt;/a&gt; if you want the longer version of this tutorial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are you putting online: a portfolio, a lab notebook, or a blog? Tell me in the comments. I read them.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the author:&lt;/strong&gt; David Raphi is a full-stack engineer and the maintainer of Nooxy. He has shipped systems for US government agencies and leads election-critical platforms. He writes about Notion, Cloudflare, and zero-dependency tooling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/draphy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/draphyofficial" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/draphy" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:contact@draphy.org"&gt;contact@draphy.org&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/README.md" rel="noopener noreferrer"&gt;Nooxy README&lt;/a&gt; and &lt;a href="https://github.com/draphy/nooxy/blob/5173afc7eddfd3e9142210087f3a3197fdd01d12/examples/cloudflare/README.md" rel="noopener noreferrer"&gt;Cloudflare deployment guide&lt;/a&gt; (pinned to the version used here)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.notion.com/pricing" rel="noopener noreferrer"&gt;Notion pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.notion.com/help/public-pages-and-web-publishing" rel="noopener noreferrer"&gt;Notion: Publish a Notion Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.notion.com/help/notion-sites-availability-and-pricing" rel="noopener noreferrer"&gt;Notion Sites availability and pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://super.so/pricing" rel="noopener noreferrer"&gt;Super.so pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stephenou/fruitionsite" rel="noopener noreferrer"&gt;Fruition repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/platform/limits/" rel="noopener noreferrer"&gt;Cloudflare Workers limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/cache/" rel="noopener noreferrer"&gt;Cloudflare Workers Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/configuration/routing/custom-domains/" rel="noopener noreferrer"&gt;Cloudflare Workers custom domains&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>notion</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Why Thousands of Developers Are Mass Migrating to PushForge for Web Push Notifications</title>
      <dc:creator>David Raphi</dc:creator>
      <pubDate>Fri, 19 Jun 2026 13:37:26 +0000</pubDate>
      <link>https://dev.to/draphy/why-thousands-of-developers-are-mass-migrating-to-pushforge-for-web-push-notifications-2eeg</link>
      <guid>https://dev.to/draphy/why-thousands-of-developers-are-mass-migrating-to-pushforge-for-web-push-notifications-2eeg</guid>
      <description>&lt;p&gt;&lt;em&gt;"crypto.createECDH is not a function" - If you've seen this error, you're not alone. Here's why the web push landscape is finally changing.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you've ever tried implementing push notifications on Cloudflare Workers, Vercel Edge, or any modern edge runtime, you've probably hit the same wall that thousands of developers face every month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: crypto.createECDH is not a function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or maybe this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: https.request is not available in edge runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These errors aren't bugs in your code. They're symptoms of a fundamental problem: &lt;strong&gt;the most popular web push library was built for a world that no longer exists&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Truth About web-push
&lt;/h2&gt;

&lt;p&gt;For years, &lt;code&gt;web-push&lt;/code&gt; has been the de facto standard for sending push notifications in Node.js. With 18+ million monthly downloads, it's the default choice that tutorials recommend, Stack Overflow answers reference, and ChatGPT suggests.&lt;/p&gt;

&lt;p&gt;But here's what those recommendations don't tell you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The Promise&lt;/th&gt;
&lt;th&gt;The Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Works everywhere"&lt;/td&gt;
&lt;td&gt;Fails on Cloudflare Workers (&lt;a href="https://github.com/web-push-libs/web-push/issues/718" rel="noopener noreferrer"&gt;#718&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Simple setup"&lt;/td&gt;
&lt;td&gt;Requires 5+ nested dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Production ready"&lt;/td&gt;
&lt;td&gt;Node.js deprecation warnings in v24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Modern JavaScript"&lt;/td&gt;
&lt;td&gt;Uses legacy &lt;code&gt;crypto.createECDH&lt;/code&gt; API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The issue has been open since 2022.&lt;/strong&gt; The workarounds are fragile. The compatibility flags are band-aids. And the edge computing revolution isn't waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter PushForge: Built for Where You're Deploying Today
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PushForge&lt;/strong&gt; is a zero-dependency web push library built entirely on the Web Crypto API, the same standard that runs in every modern browser, every edge runtime, and every JavaScript environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPushHTTPRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userSubscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This works everywhere.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No polyfills. No compatibility flags. No "it works on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comparison Developers Are Making
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;PushForge&lt;/th&gt;
&lt;th&gt;web-push&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5+ (with transitive deps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (&lt;a href="https://github.com/web-push-libs/web-push/issues/718" rel="noopener noreferrer"&gt;#718&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel Edge Functions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convex&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt;*&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deno&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;@types package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle Size&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~15KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~150KB+ with deps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;* Convex requires &lt;code&gt;"use node";&lt;/code&gt; directive&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers: Organic Growth That Speaks Volumes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;100,000+ monthly downloads&lt;/strong&gt; and growing. Zero paid marketing. Just developers solving real problems and telling others.&lt;/p&gt;

&lt;p&gt;PushForge launched in April 2025 and has seen consistent month-over-month growth as edge computing adoption accelerates. The trajectory tells you which direction the industry is moving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PushForge Works Where web-push Fails
&lt;/h2&gt;

&lt;p&gt;The difference comes down to one architectural decision: &lt;strong&gt;standards vs. legacy APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  web-push approach (Node.js specific):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ecdh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createECDH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prime256v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Node.js only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PushForge approach (Web Standards):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyPair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ECDH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;namedCurve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;P-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deriveBits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Works everywhere&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Web Crypto API is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Available in every modern browser&lt;/li&gt;
&lt;li&gt;Supported by Cloudflare Workers, Vercel Edge, Deno, Bun&lt;/li&gt;
&lt;li&gt;Part of Node.js 20+ without flags&lt;/li&gt;
&lt;li&gt;The W3C standard for cryptographic operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PushForge doesn't "polyfill" or "shim" its way to compatibility. It's built on the foundation that every JavaScript runtime shares.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Before You Trust: The Interactive Playground
&lt;/h2&gt;

&lt;p&gt;Don't take our word for it. &lt;strong&gt;Test PushForge in your browser right now:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://pushforge.draphy.org" rel="noopener noreferrer"&gt;pushforge.draphy.org&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The playground lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quick Test&lt;/strong&gt;: Enable notifications, send a test message, see it arrive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topic Channels&lt;/strong&gt;: Subscribe to named topics, target specific groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full Customization&lt;/strong&gt;: Icons, images, action buttons, vibration patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push Options&lt;/strong&gt;: Test urgency levels (battery hints), TTL, topic replacement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Browser&lt;/strong&gt;: Chrome, Firefox, Safari 16+, Edge, Brave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend? A single Cloudflare Worker using &lt;code&gt;buildPushHTTPRequest()&lt;/code&gt;. Zero dependencies. The same code you'd write.&lt;/p&gt;

&lt;p&gt;Subscriptions auto-expire (5 minutes for quick test, 1 hour for topics). No accounts. No data stored. Just push notifications, working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started in 60 Seconds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @pushforge/builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generate VAPID Keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @pushforge/builder vapid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the output: public key for your frontend, private key (JWK) for your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Subscribe Users (Frontend)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pushManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;userVisibleOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;applicationServerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VAPID_PUBLIC_KEY&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Send subscription to your server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Send Notifications (Server)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPushHTTPRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New Message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You have a notification!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/icon.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:you@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;urgency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Platform Examples Ready to Copy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Workers:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from the Edge!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vercel Edge Functions:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Edge Notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deno:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPushHTTPRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm:@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from Deno!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Makes PushForge Different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TypeScript-First, Not TypeScript-Added
&lt;/h3&gt;

&lt;p&gt;PushForge isn't a JavaScript library with type definitions bolted on. It's written in TypeScript from the ground up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;BuilderOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PushMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PushSubscription&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your IDE knows the shape of every parameter. Your compiler catches mistakes before runtime. Your code documents itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Built In, Not Bolted On
&lt;/h3&gt;

&lt;p&gt;PushForge validates everything before processing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VAPID key structure (EC P-256 curve with required x, y, d parameters)&lt;/li&gt;
&lt;li&gt;Subscription endpoints (must be valid HTTPS URLs)&lt;/li&gt;
&lt;li&gt;p256dh keys (65-byte uncompressed P-256 point format)&lt;/li&gt;
&lt;li&gt;Auth secrets (exactly 16 bytes)&lt;/li&gt;
&lt;li&gt;Payload size (max 4KB per Web Push spec)&lt;/li&gt;
&lt;li&gt;TTL bounds (max 24 hours per VAPID spec)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Invalid input fails fast with clear error messages, not cryptic failures deep in the crypto stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Roadmap: What's Coming
&lt;/h2&gt;

&lt;p&gt;PushForge is actively developed. Here's what's on the horizon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batching &amp;amp; Queuing&lt;/strong&gt;: Bulk notification delivery with rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Retry Logic&lt;/strong&gt;: Automatic error handling for push service failures
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework Examples&lt;/strong&gt;: Ready-to-use templates for React, Vue, Next.js, SvelteKit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Worker Templates&lt;/strong&gt;: Drop-in notification handling code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow development: &lt;a href="https://github.com/draphy/pushforge" rel="noopener noreferrer"&gt;github.com/draphy/pushforge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Community
&lt;/h2&gt;

&lt;p&gt;PushForge welcomes contributions. The workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open an issue describing your change&lt;/li&gt;
&lt;li&gt;Fork the repository&lt;/li&gt;
&lt;li&gt;Create a branch: &lt;code&gt;username/wpn-issuenumber-description&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Follow conventional commit guidelines&lt;/li&gt;
&lt;li&gt;Submit a pull request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The codebase uses modern tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Biome&lt;/strong&gt; for fast formatting and linting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vitest&lt;/strong&gt; for comprehensive testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Release&lt;/strong&gt; for automated versioning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; for CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Developers Are Switching
&lt;/h2&gt;

&lt;p&gt;Here's what the migration typically looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (web-push):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webpush&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;web-push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;webpush&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setVapidDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;privateKey&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Hope you're not on Cloudflare Workers...&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;webpush&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (PushForge):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPushHTTPRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Works on ANY runtime with fetch()&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is intentionally minimal. You get &lt;code&gt;endpoint&lt;/code&gt;, &lt;code&gt;headers&lt;/code&gt;, and &lt;code&gt;body&lt;/code&gt;. You use &lt;code&gt;fetch()&lt;/code&gt;. That's the entire interface, and it works everywhere &lt;code&gt;fetch()&lt;/code&gt; works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The web has evolved. Serverless is the default. Edge computing is mainstream. The tools should match the territory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PushForge is:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero dependencies (no supply chain surprises)&lt;/li&gt;
&lt;li&gt;Web standards based (no Node.js lock-in)&lt;/li&gt;
&lt;li&gt;TypeScript native (no type guessing)&lt;/li&gt;
&lt;li&gt;Edge ready (Cloudflare, Vercel, Deno, Bun - all first-class)&lt;/li&gt;
&lt;li&gt;Battle tested (100,000+ monthly downloads and growing)&lt;/li&gt;
&lt;li&gt;Open source (MIT licensed, transparent development)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;web-push served its era well.&lt;/strong&gt; But if you're deploying to modern infrastructure, you deserve a modern foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started Now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @pushforge/builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://github.com/draphy/pushforge" rel="noopener noreferrer"&gt;github.com/draphy/pushforge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/@pushforge/builder" rel="noopener noreferrer"&gt;npmjs.com/package/@pushforge/builder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playground&lt;/strong&gt;: &lt;a href="https://pushforge.draphy.org" rel="noopener noreferrer"&gt;pushforge.draphy.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;PushForge is MIT licensed and open source. Created by &lt;a href="https://github.com/draphy" rel="noopener noreferrer"&gt;David Raphi&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have questions? Open an issue. Found a bug? PRs welcome. Building something cool? I'd love to hear about it.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>🚀 Just shipped Nooxy - turn your Notion workspace into a personal website in 5 mins for free</title>
      <dc:creator>David Raphi</dc:creator>
      <pubDate>Fri, 02 Jan 2026 09:06:56 +0000</pubDate>
      <link>https://dev.to/draphy/nooxy-free-notion-to-custom-domain-personal-website-in-5-minutes-5b3a</link>
      <guid>https://dev.to/draphy/nooxy-free-notion-to-custom-domain-personal-website-in-5-minutes-5b3a</guid>
      <description>&lt;p&gt;&lt;em&gt;How an open-source Notion reverse proxy saved me from reinventing the wheel&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/draphy/nooxy" rel="noopener noreferrer"&gt;github.com/draphy/nooxy&lt;/a&gt; · &lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/nooxy" rel="noopener noreferrer"&gt;npmjs.com/package/nooxy&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment I Almost Over-Engineered Everything
&lt;/h2&gt;

&lt;p&gt;It started with a simple goal: I wanted a personal website at &lt;a href="https://draphy.org" rel="noopener noreferrer"&gt;draphy.org&lt;/a&gt;. Somewhere to put my projects, write about stuff I'm learning, maybe host my resume. Nothing fancy.&lt;/p&gt;

&lt;p&gt;My first instinct? Build a production-level CMS from scratch.&lt;/p&gt;

&lt;p&gt;I'm a developer. I know React, Next.js, databases, all that stuff. I could totally build something custom. I was already sketching database schemas in my head. User auth, CMS backend, maybe even a headless setup with Sanity or something.&lt;/p&gt;

&lt;p&gt;Then reality hit me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why am I building enterprise architecture for a site that'll get maybe 50 visitors a month?&lt;/strong&gt; This isn't a startup. Nobody's asking for features. I don't need to scale to a million users. It's literally just me putting stuff on the internet.&lt;/p&gt;

&lt;p&gt;I was about to spend weeks building something I could set up in minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Notion Problem (And Why Existing Solutions Didn't Work)
&lt;/h2&gt;

&lt;p&gt;I already document everything in Notion anyway. Notes, project ideas, random thoughts at 2am. It's all there. And Notion has this "Publish to Web" thing that lets you share pages publicly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Perfect&lt;/em&gt;, I thought. I'll just publish my Notion pages and point my custom domain to them.&lt;/p&gt;

&lt;p&gt;Then I saw the pricing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Cost of Notion's Custom Domain
&lt;/h3&gt;

&lt;p&gt;Turns out Notion buries the real cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plus Plan&lt;/strong&gt;: $10/month (annual) or $12/month (monthly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Domain Add-on&lt;/strong&gt;: Additional $8/month (annual) or $10/month (monthly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total&lt;/strong&gt;: $18-22/month just to use your own domain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a personal website that I update maybe once a week, that's &lt;strong&gt;$216-264/year&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And for that money? You still can't add custom CSS. Can't inject JavaScript. Can't control your SEO meta tags properly. You're stuck with whatever Notion decides looks good.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Search for Alternatives
&lt;/h3&gt;

&lt;p&gt;So I went looking for alternatives. Someone must have solved this already, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fruition&lt;/strong&gt; was everywhere in search results. Cloudflare Workers script, reverse proxy for Notion, open-source, free. Looked perfect.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;I checked the &lt;a href="https://github.com/stephenou/fruitionsite" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. &lt;strong&gt;189 open issues.&lt;/strong&gt; Blank screens, broken navigation, databases not rendering after some Notion update in 2023. The project's basically dead.&lt;/p&gt;

&lt;p&gt;People were frustrated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Site is not loading... a white screen with an infinitely looping loading circle."&lt;/p&gt;

&lt;p&gt;"Back button functionality is broken, causing the signup screen to appear when using browser back navigation."&lt;/p&gt;

&lt;p&gt;"Database not rendered - after Notion update 09.2023"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The project is effectively abandoned, with dozens of people scrambling to piece together workarounds that take hours to implement. And even then, there's no guarantee they'll work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NoteHost&lt;/strong&gt; was another option. At least it's an actual &lt;a href="https://www.npmjs.com/package/notehost" rel="noopener noreferrer"&gt;npm package&lt;/a&gt;, which was nice. But last update was like 10 months ago, barely any community around it, and it was missing stuff I wanted.&lt;/p&gt;

&lt;p&gt;I tried a few other GitHub repos too. &lt;code&gt;notion-custom-domain&lt;/code&gt;, &lt;code&gt;notion-proxy&lt;/code&gt;, things like that. Same story everywhere: outdated, barely documented, half-working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Why Not Build It Myself?" Moment
&lt;/h2&gt;

&lt;p&gt;After wasting an entire Saturday on broken Fruition configs and reading through GitHub issues that went nowhere, I just stopped.&lt;/p&gt;

&lt;p&gt;Wait. I'm a developer. I know how reverse proxies work. Why am I fighting someone else's broken code when I could just... write my own?&lt;/p&gt;

&lt;p&gt;So that's what I did. I called it &lt;strong&gt;Nooxy&lt;/strong&gt; because all the good names were taken and I was tired.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Nooxy?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/draphy/nooxy" rel="noopener noreferrer"&gt;Nooxy&lt;/a&gt; is a Notion reverse proxy. TypeScript, zero dependencies, works on Cloudflare Workers or Node.js. You point it at your Notion pages, it serves them on your domain.&lt;/p&gt;

&lt;p&gt;What's actually different about it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero Dependencies
&lt;/h3&gt;

&lt;p&gt;I went a bit obsessive here, but no regrets. Zero runtime dependencies means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster cold starts&lt;/strong&gt; on edge computing platforms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smaller bundle size&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No security vulnerabilities&lt;/strong&gt; from third-party packages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dependency conflicts&lt;/strong&gt; or version mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you deploy to Cloudflare Workers, it's just your code. No bloat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Works Where You Need It
&lt;/h3&gt;

&lt;p&gt;Runs on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; (what I use)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js 18.17+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Anything with &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt; support really&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The 5-Minute Setup
&lt;/h3&gt;

&lt;p&gt;If you already have a Notion page published, you can have your custom domain working in under 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install nooxy&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;nooxy

&lt;span class="c"&gt;# 2. Initialize configuration&lt;/span&gt;
npx nooxy init

&lt;span class="c"&gt;# 3. Edit your config (add your domain and Notion page IDs)&lt;/span&gt;
&lt;span class="c"&gt;# 4. Generate required files&lt;/span&gt;
npx nooxy generate

&lt;span class="c"&gt;# 5. Deploy to Cloudflare Workers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's literally it. I spent more time picking the domain name than setting this up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stuff That Actually Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clean URLs with Slug Mapping
&lt;/h3&gt;

&lt;p&gt;Instead of ugly Notion URLs like &lt;code&gt;notion.so/My-Page-abc123def456...&lt;/code&gt;, you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;slugToPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_HOME_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ABOUT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/projects&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PROJECTS_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_BLOG_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your visitors see &lt;code&gt;yourdomain.com/about&lt;/code&gt;, not a 32-character hash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Customization Control
&lt;/h3&gt;

&lt;p&gt;Inject custom CSS, JavaScript, and HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* nooxy/head.css */&lt;/span&gt;
&lt;span class="nc"&gt;.notion-topbar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Hide Notion's default navigation */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.notion-page-content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add custom functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// nooxy/body.js&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add smooth scrolling, analytics, custom interactions... whatever you need&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Notion site, your rules.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SEO That Doesn't Suck
&lt;/h3&gt;

&lt;p&gt;Notion's SEO is pretty bad out of the box. With Nooxy you can actually control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meta tags per page&lt;/strong&gt;: Titles, descriptions, the works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Graph stuff&lt;/strong&gt;: So your links don't look terrible when shared&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter Cards&lt;/strong&gt;: Same deal for Twitter/X&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-LD&lt;/strong&gt;: For the SEO nerds (I am one)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pageMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'My Custom Title - Draphy',&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'A description optimized for search engines',&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourdomain.com/og-image.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;David Raphi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local Development Support
&lt;/h3&gt;

&lt;p&gt;Test your site locally before deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Nooxy automatically detects localhost and adjusts&lt;/span&gt;
&lt;span class="c1"&gt;// No special configuration needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;wrangler dev&lt;/code&gt; and see your changes instantly at &lt;code&gt;localhost:8787&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Instance Support
&lt;/h3&gt;

&lt;p&gt;Running multiple Notion sites? Nooxy supports that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prodProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productionConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stagingProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeNooxy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;configKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stagingConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each instance gets its own cache and configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Minification
&lt;/h3&gt;

&lt;p&gt;The CLI automatically minifies your custom CSS, JavaScript, and HTML during the build process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate  &lt;span class="c"&gt;# Minifies by default&lt;/span&gt;
npx nooxy generate &lt;span class="nt"&gt;--no-minify&lt;/span&gt;  &lt;span class="c"&gt;# Skip minification if needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Stacks Up (Honestly)
&lt;/h2&gt;

&lt;p&gt;Look, I built this thing so I'm biased. But here's the actual comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Nooxy&lt;/th&gt;
&lt;th&gt;Fruition&lt;/th&gt;
&lt;th&gt;NoteHost&lt;/th&gt;
&lt;th&gt;Notion Sites&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$18-22/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom Domain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom CSS/JS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SEO Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zero Dependencies&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actively Maintained&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (189 issues)&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CLI Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local Dev Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-instance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Use Nooxy If...
&lt;/h3&gt;

&lt;p&gt;You'll probably like it if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't want to pay monthly for something this simple&lt;/li&gt;
&lt;li&gt;Want to actually customize your site (CSS, JS, whatever)&lt;/li&gt;
&lt;li&gt;Care about SEO&lt;/li&gt;
&lt;li&gt;Like testing locally before deploying&lt;/li&gt;
&lt;li&gt;Are fine with Cloudflare Workers or Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Skip Nooxy If...
&lt;/h3&gt;

&lt;p&gt;Not gonna pretend it's for everyone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You hate terminals (you'll need to run a few commands)&lt;/li&gt;
&lt;li&gt;You want official Notion support (this is just me)&lt;/li&gt;
&lt;li&gt;You're not using Cloudflare or Node&lt;/li&gt;
&lt;li&gt;You need fancy analytics dashboards built-in&lt;/li&gt;
&lt;li&gt;You need visitors to see real-time edits or collaboration stuff&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What About Just Paying Notion?
&lt;/h3&gt;

&lt;p&gt;Fair question. Notion Sites isn't terrible if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have the budget ($18-22/month adds up though)&lt;/li&gt;
&lt;li&gt;Don't care about custom styling&lt;/li&gt;
&lt;li&gt;Just want it to work with zero effort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing wrong with that. I just didn't want another monthly subscription for something I could build in a weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It's Going For Me
&lt;/h2&gt;

&lt;p&gt;Been running &lt;a href="https://os.draphy.org" rel="noopener noreferrer"&gt;os.draphy.org&lt;/a&gt; on Nooxy for a while now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;: Took like 5 minutes. Most of that was finding my Notion page IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed&lt;/strong&gt;: It's on Cloudflare's edge network so it's fast everywhere. Haven't had complaints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;: Basically none. I forget it's even running most of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization&lt;/strong&gt;: I've got my own nav, custom styles, proper SEO. Stuff that Notion just won't let you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Want to try it? Here's the quick version:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install and Initialize
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nooxy
npx nooxy init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;nooxy/&lt;/code&gt; directory with all configuration files.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Configure Your Site
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;nooxy/config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SITE_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;notionDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-workspace.notion.site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Site Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;slugToPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_HOME_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_ABOUT_PAGE_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// These are auto-generated, just import them&lt;/span&gt;
  &lt;span class="na"&gt;customHeadCSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HEAD_CSS_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customHeadJS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HEAD_JS_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customBodyJS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BODY_JS_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HEADER_HTML_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Find Your Notion Page IDs
&lt;/h3&gt;

&lt;p&gt;Open your Notion page → Share → Copy link. The ID is the 32-character string at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notion.so/My-Page-Title-abc123def456789012345678901234
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        This is your page ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Generate and Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx nooxy generate  &lt;span class="c"&gt;# Creates minified string files&lt;/span&gt;

&lt;span class="c"&gt;# Then deploy to Cloudflare Workers&lt;/span&gt;
wrangler deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Point Your Domain
&lt;/h3&gt;

&lt;p&gt;In Cloudflare, add a CNAME record pointing to your Worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It This Way
&lt;/h2&gt;

&lt;p&gt;A few things I kept in mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't overbuild.&lt;/strong&gt; A personal site doesn't need a CMS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependencies are debt.&lt;/strong&gt; Every package you add is a future breaking change waiting to happen. I wanted something I wouldn't have to fix every time some library updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make it actually usable.&lt;/strong&gt; CLI tools, TypeScript, local dev. Not because they're trendy but because I actually wanted to use this thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Found a Bug? Want to Contribute?
&lt;/h2&gt;

&lt;p&gt;It's open source (MIT). PRs welcome.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt;: &lt;a href="https://github.com/draphy/nooxy" rel="noopener noreferrer"&gt;github.com/draphy/nooxy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues&lt;/strong&gt;: &lt;a href="https://github.com/draphy/nooxy/issues" rel="noopener noreferrer"&gt;github.com/draphy/nooxy/issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discussions&lt;/strong&gt;: &lt;a href="https://github.com/draphy/nooxy/discussions" rel="noopener noreferrer"&gt;github.com/draphy/nooxy/discussions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;I wanted a personal website. I almost spent weeks building a CMS I didn't need. Then I almost paid Notion $20/month for features I could build myself.&lt;/p&gt;

&lt;p&gt;Instead I spent a weekend building Nooxy. Now my site runs for free, looks how I want, and I don't think about it.&lt;/p&gt;

&lt;p&gt;If you use Notion and want a custom domain without the subscription, maybe give it a try. Five minutes, free, you own everything.&lt;/p&gt;

&lt;p&gt;If it's useful, &lt;a href="https://github.com/draphy/nooxy" rel="noopener noreferrer"&gt;star it on GitHub&lt;/a&gt;. If it's broken, open an issue. Either way, thanks for reading.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the author&lt;/strong&gt;: David Raphi is a full-stack engineer who's shipped systems for US government agencies (Florida DCF, Republican Party of Florida) and led teams delivering election-critical platforms in 1-week sprints. Lives in Cloudflare's stack and has a thing for zero-dependency packages (PushForge is another one).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/draphy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/draphyofficial" rel="noopener noreferrer"&gt;Twitter/X&lt;/a&gt; · &lt;a href="https://linkedin.com/in/draphy" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:contact@draphy.org"&gt;contact@draphy.org&lt;/a&gt; · &lt;a href="mailto:david@draphy.org"&gt;david@draphy.org&lt;/a&gt;&lt;/p&gt;




</description>
      <category>opensource</category>
      <category>notion</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🚀PushForge: Modern Web Push Notifications Made Simple🚀</title>
      <dc:creator>David Raphi</dc:creator>
      <pubDate>Tue, 22 Apr 2025 21:44:30 +0000</pubDate>
      <link>https://dev.to/draphy/pushforge-modern-web-push-notifications-made-simple-599p</link>
      <guid>https://dev.to/draphy/pushforge-modern-web-push-notifications-made-simple-599p</guid>
      <description>&lt;p&gt;I'm thrilled to introduce &lt;strong&gt;PushForge v1.0.0&lt;/strong&gt;, my latest open-source contribution to the web development ecosystem. PushForge is a zero-dependency toolkit designed to make Web Push Notifications implementation seamless across multiple platforms including Node.js, Browsers, Deno, Bun, and even Cloudflare Workers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/draphy/pushforge" rel="noopener noreferrer"&gt;github.com/draphy/pushforge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/org/pushforge" rel="noopener noreferrer"&gt;npmjs.com/org/pushforge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;builder&lt;/strong&gt;: &lt;a href="https://npmjs.com/package/@pushforge/builder" rel="noopener noreferrer"&gt;npmjs.com/package/@pushforge/builder&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Built PushForge ❓
&lt;/h2&gt;

&lt;p&gt;When my team needed push notifications for a project, we discovered existing libraries were unnecessarily heavy and restrictive, especially for edge computing environments. This constraint led us to develop a custom solution, which eventually inspired me to create a more comprehensive package with broader compatibility.&lt;/p&gt;

&lt;p&gt;PushForge addresses these challenges by providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lightweight solution with zero external dependencies&lt;/li&gt;
&lt;li&gt;Cross-platform compatibility for modern JavaScript environments&lt;/li&gt;
&lt;li&gt;TypeScript-first development for robust applications&lt;/li&gt;
&lt;li&gt;Complete control over the notification process&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔑 Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cross-Platform Compatibility
&lt;/h3&gt;

&lt;p&gt;PushForge works seamlessly across multiple environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (v16+)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Browsers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deno&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bun&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This versatility ensures that regardless of your tech stack, you can implement push notifications without platform-specific workarounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero Dependencies
&lt;/h3&gt;

&lt;p&gt;PushForge is engineered to be lightweight and self-contained, making it especially valuable for edge computing environments where bundle size is critical. By eliminating external dependencies, we reduce potential security vulnerabilities and ensure a smaller footprint in your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeScript-First Design
&lt;/h3&gt;

&lt;p&gt;The entire library is built with TypeScript, providing comprehensive type definitions out of the box. This approach delivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved developer experience with autocomplete suggestions&lt;/li&gt;
&lt;li&gt;Type safety to catch errors during development&lt;/li&gt;
&lt;li&gt;Better documentation through type annotations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Complete VAPID Authentication &amp;amp; Web Push Protocol Support
&lt;/h3&gt;

&lt;p&gt;PushForge implements the full Web Push Protocol specification, ensuring your notifications are secure and compatible with all modern browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Technical Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web Push Protocol Implementation
&lt;/h3&gt;

&lt;p&gt;PushForge implements several critical components of the Web Push Protocol:&lt;/p&gt;

&lt;h4&gt;
  
  
  VAPID Authentication (JWT with ES256)
&lt;/h4&gt;

&lt;p&gt;PushForge uses the ES256 algorithm (ECDSA using P-256 curve and SHA-256 hash) to create and verify JSON Web Tokens (JWTs) for Voluntary Application Server Identification (VAPID). This process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates a JWT using the server's private key&lt;/li&gt;
&lt;li&gt;Signs it with the ES256 algorithm&lt;/li&gt;
&lt;li&gt;Includes the server's contact information&lt;/li&gt;
&lt;li&gt;Sets appropriate expiration times (up to 24 hours)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resulting authorization header allows push services to validate that your server is authorized to send notifications to the user.&lt;/p&gt;

&lt;h4&gt;
  
  
  ECDH Key Exchange
&lt;/h4&gt;

&lt;p&gt;Elliptic Curve Diffie-Hellman (ECDH) key exchange enables secure communication between your server and the user's browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser generates a public/private key pair during subscription&lt;/li&gt;
&lt;li&gt;Your server receives the public key (&lt;code&gt;p256dh&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;PushForge uses ECDH to create a shared secret using your server's generated key pair and the user's public key&lt;/li&gt;
&lt;li&gt;This shared secret becomes the basis for encryption&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  HKDF Key Derivation
&lt;/h4&gt;

&lt;p&gt;HMAC-based Key Derivation Function (HKDF) transforms the shared ECDH secret into appropriate encryption keys:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Combines the shared secret with the authentication secret (&lt;code&gt;auth&lt;/code&gt;) provided by the browser&lt;/li&gt;
&lt;li&gt;Uses a salt value (randomly generated for each message)&lt;/li&gt;
&lt;li&gt;Derives encryption key and nonce (initialization vector) for AES-GCM&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  AES-GCM Encryption
&lt;/h4&gt;

&lt;p&gt;Advanced Encryption Standard with Galois/Counter Mode (AES-GCM):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypts the notification payload using the derived encryption key&lt;/li&gt;
&lt;li&gt;Provides authenticated encryption (data integrity and confidentiality)&lt;/li&gt;
&lt;li&gt;Incorporates appropriate padding to obscure message length&lt;/li&gt;
&lt;li&gt;Outputs the encrypted content ready for transmission&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Compliant Implementation Details
&lt;/h4&gt;

&lt;p&gt;PushForge follows RFC 8291 (Message Encryption for Web Push) standards, ensuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content-Encoding: aes128gcm format&lt;/li&gt;
&lt;li&gt;Proper salt generation and record size limits&lt;/li&gt;
&lt;li&gt;Correct padding implementation&lt;/li&gt;
&lt;li&gt;Authentication tag handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚒️ Development Workflow
&lt;/h2&gt;

&lt;p&gt;PushForge employs modern development tools to ensure code quality and streamlined contributions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic Release
&lt;/h3&gt;

&lt;p&gt;Automated versioning and changelog generation based on conventional commit messages, which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determines version numbers automatically (major, minor, patch)&lt;/li&gt;
&lt;li&gt;Generates detailed release notes&lt;/li&gt;
&lt;li&gt;Publishes updates to npm when merging to main branch&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Actions for CI/CD
&lt;/h3&gt;

&lt;p&gt;Continuous integration and deployment pipelines that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the test suite on every pull request&lt;/li&gt;
&lt;li&gt;Verify code formatting and linting&lt;/li&gt;
&lt;li&gt;Ensure type checking passes&lt;/li&gt;
&lt;li&gt;Automate the release process&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Biome for Code Quality
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://biomejs.dev/" rel="noopener noreferrer"&gt;Biome&lt;/a&gt; provides fast, modern formatting and linting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforces consistent code style&lt;/li&gt;
&lt;li&gt;Catches potential bugs early&lt;/li&gt;
&lt;li&gt;Runs as part of pre-commit checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testing with Vitest
&lt;/h3&gt;

&lt;p&gt;Comprehensive test coverage using &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests for core functionality&lt;/li&gt;
&lt;li&gt;Integration tests for different environments&lt;/li&gt;
&lt;li&gt;Fast execution through Vite's architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Management with Linear
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://linear.app/" rel="noopener noreferrer"&gt;Linear&lt;/a&gt; helps track issues and plan development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manages the feature roadmap&lt;/li&gt;
&lt;li&gt;Coordinates sprint planning&lt;/li&gt;
&lt;li&gt;Keeps issue backlog organized&lt;/li&gt;
&lt;li&gt;Integrates with GitHub for streamlined workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 When to Use PushForge
&lt;/h2&gt;

&lt;p&gt;PushForge is particularly well-suited for projects that require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edge Computing Compatibility&lt;/strong&gt;: Deploy to serverless or edge environments without worry about dependencies or platform limitations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript Projects&lt;/strong&gt;: Benefit from full type safety and improved developer experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Push Logic&lt;/strong&gt;: Implement complex notification strategies with full control over the process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Platform Solutions&lt;/strong&gt;: Build unified notification systems that work across different JavaScript environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📈 Future Plans
&lt;/h2&gt;

&lt;p&gt;I'm actively working on expanding PushForge with these planned features:&lt;/p&gt;

&lt;h3&gt;
  
  
  Batching &amp;amp; Queuing System
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Efficient bulk notification delivery&lt;/li&gt;
&lt;li&gt;Rate limiting and automatic retry mechanisms&lt;/li&gt;
&lt;li&gt;Priority-based queuing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Built-in Request Functionality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Currently, you provide your own fetch implementation&lt;/li&gt;
&lt;li&gt;Future versions will include built-in request handling&lt;/li&gt;
&lt;li&gt;Support for automatic error handling and retries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Legacy GCM Support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Backward compatibility for older notification systems&lt;/li&gt;
&lt;li&gt;Support for Google Cloud Messaging legacy endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Comprehensive Examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ready-to-use examples for popular frameworks (React, Vue, Angular, etc.)&lt;/li&gt;
&lt;li&gt;Server-side implementations for Node.js, Express, Fastify, etc.&lt;/li&gt;
&lt;li&gt;Service worker templates for quick implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started with PushForge
&lt;/h2&gt;

&lt;p&gt;Here's a quick guide to implementing PushForge in your project:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Installation
&lt;/h3&gt;

&lt;p&gt;Choose your preferred package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# NPM&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @pushforge/builder

&lt;span class="c"&gt;# Yarn&lt;/span&gt;
yarn add @pushforge/builder

&lt;span class="c"&gt;# pnpm&lt;/span&gt;
pnpm add @pushforge/builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Generate VAPID Keys
&lt;/h3&gt;

&lt;p&gt;Use the built-in CLI tool to generate your VAPID keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @pushforge/builder generate-vapid-keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output a public key and private key that you'll use for authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Send Notifications from Your Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPushHTTPRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@pushforge/builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load the private key from your secure environment&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateJWK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VAPID_PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// User subscription from browser push API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://fcm.googleapis.com/fcm/send/DEVICE_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;p256dh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_PUBLIC_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_AUTH_SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create message with payload&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New Message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You have a new message!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/images/icon.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Time-to-live in seconds&lt;/span&gt;
    &lt;span class="na"&gt;urgency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Options: "very-low", "low", "normal", "high"&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;updates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Optional topic for replacing notifications&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;adminContact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailto:admin@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Contact information&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Build the push notification request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildPushHTTPRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;privateJWK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send the push notification&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🫂 Join the Community
&lt;/h2&gt;

&lt;p&gt;Contributions to PushForge are welcome and encouraged! The project follows a structured workflow for contributions as outlined in the &lt;a href="https://github.com/draphy/pushforge/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;Contributing Guidelines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whether you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Report bugs or suggest features&lt;/li&gt;
&lt;li&gt;Improve tests or documentation&lt;/li&gt;
&lt;li&gt;Submit pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how to contribute:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an issue in the GitHub tracker describing your proposed change&lt;/li&gt;
&lt;li&gt;Wait for a Linear issue number to be assigned&lt;/li&gt;
&lt;li&gt;Fork the repository and create a branch with the proper naming convention:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   username/wpn-issuenumber-issuetitle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Make your changes following our &lt;a href="https://github.com/draphy/pushforge/blob/main/CONTRIBUTING.md#6-commit-guidelines" rel="noopener noreferrer"&gt;commit guidelines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Submit a pull request linking to the issue&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's build the best push notification solution together! 💪&lt;/p&gt;

&lt;h2&gt;
  
  
  📖 References
&lt;/h2&gt;

&lt;p&gt;My work on PushForge was informed by these key resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc8291" rel="noopener noreferrer"&gt;RFC 8291: Message Encryption for Web Push&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API" rel="noopener noreferrer"&gt;Web Crypto API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_VAPID_keys_for_application_server_key" rel="noopener noreferrer"&gt;VAPID Keys for Web Push&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc7518#section-3.4" rel="noopener noreferrer"&gt;JSON Web Algorithms (RFC 7518)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman" rel="noopener noreferrer"&gt;Elliptic‑curve Diffie–Hellman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc5869" rel="noopener noreferrer"&gt;HKDF (RFC 5869)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Encryption/Using_AES-GCM" rel="noopener noreferrer"&gt;AES‑GCM Encryption&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I'm excited to see what you'll build with PushForge! Feel free to star the repository, open issues, or reach out with questions. Together, we can make web push notifications simpler and more accessible for developers everywhere.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
