DEV Community

Cover image for How to Host a Personal Website for Free in 10 Minutes (Notion + Cloudflare)
David Raphi
David Raphi

Posted on

How to Host a Personal Website for Free in 10 Minutes (Notion + Cloudflare)

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.

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.

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.

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 draphy.org, os.draphy.org, and em-ucd.com. One Nooxy user also built a fan-made Expedition 33 music wiki that documents 196 songs across four albums. It is available in English and Spanish.

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 *.workers.dev URL if you do not have one yet.

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.

Last checked: October 2026. Nooxy v2.0.0.

The short version

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

Jump to: The 10-minute setup · SEO checklist · Troubleshooting · The main trade-offs · FAQ

Why I built this

I wanted a personal site at draphy.org. I already documented everything in Notion, so paying another $20 a month for a CMS felt backwards.

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.

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.

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.

What you actually need

Requirement Why you need it Cost
Node.js 22 or newer Runs the Nooxy CLI and Wrangler Free
A Cloudflare account Hosts the Worker and manages DNS Free, no credit card for Workers Free
At least one published Notion page Your content Free plan is enough for a solo site
A domain name (optional) A professional URL Roughly $10 to $15 per year
10 to 15 minutes Setup Your time

Check your Node version first:

node -v
Enter fullscreen mode Exit fullscreen mode

If it prints v22 or higher, you are ready. If not, install the current LTS from nodejs.org. Nooxy requires Node 22, so an old global Node install is the most common reason the CLI fails.

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.

How Notion, Nooxy, and Cloudflare fit together

The architecture is intentionally boring.

Visitor -> 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
Enter fullscreen mode Exit fullscreen mode

Notion is the CMS. You write and edit there. Published changes show up on the site without a rebuild.

Nooxy is the translation layer. It maps a clean path like /about to a Notion page ID, fetches the public page, and rewrites the response. It removes Notion's noindex 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 /sitemap.xml and /robots.txt.

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 Cloudflare's docs.

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.

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.

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.

What "free" actually means

A custom domain is not free, and I do not want anyone to start this guide thinking otherwise.

Here is the real cost picture for this stack, checked in October 2026:

What What it costs Notes
Cloudflare Workers $0 100,000 requests per day on the free plan
TLS certificate $0 Created and renewed by Cloudflare
Notion $0 The free plan is enough for a solo personal site
Nooxy $0 MIT licensed
Custom domain Roughly $10 to $15 per year Optional. The *.workers.dev URL is free

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.

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

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.

The 10-minute setup

You will do 11 steps. Steps 1 through 8 get you a live site on a workers.dev 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.

If you get stuck, the full Cloudflare deployment guide 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.

Step 1: Create the Cloudflare project

In an empty folder you want to work in, run:

npm create cloudflare@latest my-nooxy-site
Enter fullscreen mode Exit fullscreen mode

You will be asked a few questions. Choose these options:

Question What to select
What would you like to start with? Hello World example
Which template? Hello World Worker
Which language? TypeScript
Do you want to use git? Your choice. Yes is fine
Do you want to deploy? No, we will do it later

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.

Move into the project:

cd my-nooxy-site
Enter fullscreen mode Exit fullscreen mode

At this point you should have src/index.ts, package.json, and wrangler.jsonc.

Step 2: Install Nooxy and create the config

Run these inside my-nooxy-site:

npm install nooxy
npx nooxy init
Enter fullscreen mode Exit fullscreen mode

npx nooxy init creates a nooxy/ folder:

my-nooxy-site/
├── src/
│   └── index.ts
├── nooxy/
│   ├── config.js
│   ├── head.css
│   ├── head.js
│   ├── body.js
│   └── header.html
├── package.json
└── wrangler.jsonc
Enter fullscreen mode Exit fullscreen mode

The four optional files are where your custom CSS, JavaScript, and header HTML go. You can leave them empty for now.

Step 3: Publish your Notion pages and copy the IDs

Every Notion page has a 32-character page ID. Nooxy uses that ID to connect a URL path to a page.

For each page you want on your site:

  1. Open the page in Notion.
  2. Click Share in the top right.
  3. Open the Publish tab.
  4. Turn on Publish to web or click Publish.
  5. Copy the public link.

The published link looks like this:

https://your-workspace.notion.site/Your-Page-Title-abc123def456789012345678901234ab
Enter fullscreen mode Exit fullscreen mode

The page ID is the 32 hex characters at the end of the URL path, after the last hyphen:

abc123def456789012345678901234ab
Enter fullscreen mode Exit fullscreen mode

Ignore anything after a ?, such as ?pvs=4. That is a tracking parameter, not part of the ID.

The hostname, your-workspace.notion.site, is your notionDomain. You will need both the ID and the hostname in the next step.

A few details that save time:

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

Check your subpages before you publish. 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.

Notion explains the publishing flow in Publish a Notion Site.

Step 4: Configure the site

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

The four required values look like this:

export const SITE_CONFIG = {
  // Your site's hostname, without https://
  // You can start with your workers.dev URL and update it after the first deploy.
  domain: 'my-nooxy-site.YOUR-SUBDOMAIN.workers.dev',

  // The hostname of your published Notion site
  notionDomain: 'your-workspace.notion.site',

  // Your name or site name
  siteName: 'Your Name',

  // URL path -> 32-character Notion page ID
  slugToPage: {
    '/': 'YOUR_HOME_PAGE_ID',
    '/about': 'YOUR_ABOUT_PAGE_ID',
    '/projects': 'YOUR_PROJECTS_PAGE_ID',
  },

  // Leave the rest of the file as it is.
};
Enter fullscreen mode Exit fullscreen mode

Your actual file also has seo, pageMetadata, fof, subDomains, and other options. They are commented out by default. You can uncomment the ones you need later. The configuration reference lists every field.

For a basic site, add this seo block inside SITE_CONFIG:

seo: {
  indexing: true,
  defaultAuthor: 'Your Name',
  brandReplacement: 'Your Name',
},
Enter fullscreen mode Exit fullscreen mode

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

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

One common mistake: domain is a hostname, not a full URL. Use example.com, not https://example.com.

Step 5: Generate the injected assets

Run:

npx nooxy generate
Enter fullscreen mode Exit fullscreen mode

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

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

Step 6: Connect Nooxy to the Worker

Open src/index.ts and replace everything in it with this:

import { initializeNooxy } from 'nooxy';
import { SITE_CONFIG } from '../nooxy/config';

const proxy = initializeNooxy(SITE_CONFIG);

export default {
  async fetch(request: Request): Promise<Response> {
    return proxy(request);
  },
};
Enter fullscreen mode Exit fullscreen mode

Save the file. That is the entire integration. initializeNooxy returns a function that takes a standard Request and returns a standard Response.

Step 7: Test locally

Start the local Worker:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:8787. You should see your Notion page, served by Nooxy.

Press Ctrl + C when you are done.

If it does not work, check these first:

Problem What to check
Page not found Is the page published? Is the page ID exactly 32 characters? Does notionDomain match the published hostname?
Blank page The page ID is usually wrong. Copy it again from the published URL
Cannot find module 'nooxy' Run npm install nooxy in the project root
Port already in use Stop the other process, or let Wrangler pick another port

Nooxy automatically uses localhost for canonical URLs during local development, so you do not need to change domain just to test.

Step 8: Deploy

Log in to Cloudflare once:

npx wrangler login
Enter fullscreen mode Exit fullscreen mode

This opens a browser window. Click Allow.

Then deploy:

npm run deploy
Enter fullscreen mode Exit fullscreen mode

When it finishes, Wrangler prints a URL like this:

https://my-nooxy-site.your-subdomain.workers.dev
Enter fullscreen mode Exit fullscreen mode

Open it. Your Notion page is now live on the internet.

If that URL does not match the domain in nooxy/config.js, update domain, then run:

npx nooxy generate
npm run deploy
Enter fullscreen mode Exit fullscreen mode

You now have a working site. The rest is making it yours.

Step 9: Put it on your own domain

This is the part that makes the site feel real. It is also the only part that can cost money.

If you do not have a domain yet, buy one from Cloudflare Registrar, Namecheap, or Porkbun. A .com is usually in the $10 to $15 per year range. Prices vary by extension. Do not buy hosting. You do not need it.

If your domain is already on Cloudflare

  1. Go to Cloudflare Dashboard and open Workers & Pages.
  2. Select your Worker.
  3. Go to Settings > Domains & Routes.
  4. Click Add > Custom Domain.
  5. Enter your domain, for example example.com, and confirm.

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.

If your domain is not on Cloudflare yet

  1. In the Cloudflare dashboard, go to Websites > Add a Site.
  2. Enter your domain and choose the Free plan.
  3. Cloudflare gives you two nameservers.
  4. At your registrar, replace the current nameservers with Cloudflare's.
  5. Wait until the domain shows Active. This is usually under an hour, but it can take up to 24 hours.
  6. Then follow the Custom Domain steps above.

Update the config

Once the domain is attached, change domain:

domain: 'example.com',
Enter fullscreen mode Exit fullscreen mode

Then run:

npx nooxy generate
npm run deploy
Enter fullscreen mode Exit fullscreen mode

Make www work

example.com and www.example.com are different hostnames. Adding only one will not make the other work.

For SEO, pick one canonical version and redirect the other. The usual choice is www to apex:

  1. Add www.example.com as a second Custom Domain on the same Worker.
  2. Add this to nooxy/config.js:
subDomains: {
  www: {
    redirect: 'https://example.com',
  },
},
Enter fullscreen mode Exit fullscreen mode
  1. Run npx nooxy generate and npm run deploy.

Now https://www.example.com returns a 301 redirect to https://example.com. All traffic and link equity point at one hostname.

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.

The Cloudflare custom domain docs confirm the important detail: Custom Domains require an exact hostname match, and Cloudflare creates the DNS record and certificate for you.

Step 10: Add more pages

To add a page:

  1. Create it in Notion.
  2. Publish it.
  3. Copy the 32-character page ID.
  4. Add a path and ID to slugToPage:
slugToPage: {
  '/': 'YOUR_HOME_PAGE_ID',
  '/about': 'YOUR_ABOUT_PAGE_ID',
  '/projects': 'YOUR_PROJECTS_PAGE_ID',
  '/contact': 'YOUR_CONTACT_PAGE_ID',
  '/docs/getting-started': 'YOUR_DOCS_PAGE_ID',
},
Enter fullscreen mode Exit fullscreen mode
  1. Run:
npx nooxy generate
npm run deploy
Enter fullscreen mode Exit fullscreen mode

Nested paths work. The sitemap is generated from this map, so any page you want search engines to discover should be in slugToPage.

Step 11: Make it yours

Nooxy gives you four injection points. They are all optional.

File What it does When it runs
nooxy/head.css Custom CSS Injected into <head>
nooxy/head.js Early JavaScript Before page content loads
nooxy/body.js Main JavaScript Before </body>
nooxy/header.html Custom header HTML At the top of the page body

A small CSS starting point:

/* nooxy/head.css */
.notion-topbar {
  display: none !important;
}

.notion-page-content {
  max-width: 900px;
  margin: 0 auto;
}
Enter fullscreen mode Exit fullscreen mode

A simple header:

<!-- nooxy/header.html -->
<nav class="site-nav">
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/projects">Projects</a>
  <a href="/contact">Contact</a>
</nav>
Enter fullscreen mode Exit fullscreen mode

After any change in nooxy/, run:

npx nooxy generate
npm run deploy
Enter fullscreen mode Exit fullscreen mode

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 !important. The project files documentation has examples.

Optional extras you may want later

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 nooxy/config.js:

Setting What it does
fof: { page: 'YOUR_404_PAGE_ID', slug: '/404' } Sends unknown paths to a published Notion page instead of a plain 404
googleFont: 'Inter' Loads a Google Font site-wide
nooxy: { showBadge: false } Hides the small "Made with Nooxy" badge, which is shown by default
configKey Lets one Worker serve several sites, each with its own config

One SEO caveat on fof: 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 fof off.

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

const siteA = initializeNooxy({ configKey: 'site-a', config: SITE_A });
const siteB = initializeNooxy({ configKey: 'site-b', config: SITE_B });
Enter fullscreen mode Exit fullscreen mode

Then choose the site by hostname in your fetch handler. The README section on hosting several sites has the complete example. If you omit configKey, Nooxy falls back to the site's domain, but an explicit key is clearer and is required when two sites share a domain.

As always, run npx nooxy generate and npm run deploy after changing the config.

Turn on Workers Cache

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.

Two changes. First, turn it on in wrangler.jsonc:

"cache": {
  "enabled": true
},
Enter fullscreen mode Exit fullscreen mode

This needs Wrangler 4.69 or newer. The project you created in Step 1 already has a current Wrangler version.

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

import { initializeNooxy } from 'nooxy';
import { SITE_CONFIG } from '../nooxy/config';

const proxy = initializeNooxy(SITE_CONFIG);

export default {
  async fetch(request: Request): Promise<Response> {
    const response = await proxy(request);
    const headers = new Headers(response.headers);
    const type = headers.get('content-type') || '';

    if (response.status === 200 && type.includes('text/html')) {
      headers.set('cloudflare-cdn-cache-control', 'public, max-age=300');
    }

    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers,
    });
  },
};
Enter fullscreen mode Exit fullscreen mode

cloudflare-cdn-cache-control 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.

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.

Deploy, then check it:

curl -I https://example.com/
curl -I https://example.com/
Enter fullscreen mode Exit fullscreen mode

The second response should include cf-cache-status: HIT. The first is a miss while the cache fills.

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 configKey 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.

If you would rather keep every request live, leave the cache off. The site works fine without it.

The SEO checklist after launch

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.

1. Check the three URLs

Open these in a browser:

https://example.com/robots.txt
https://example.com/sitemap.xml
https://example.com/
Enter fullscreen mode Exit fullscreen mode

robots.txt should point to your sitemap. sitemap.xml should list every slug in slugToPage. The homepage should load.

2. Inspect the page source

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

Search for:

  • <link rel="canonical" and confirm it points to your domain, not notion.site.
  • <meta name="robots" content="index, follow and confirm it is present.
  • <title> and <meta name="description">.
  • <script type="application/ld+json"> and confirm the WebPage schema and its nested WebSite.

If the canonical URL points to notion.site, your domain value is wrong. Fix it and redeploy.

3. Add per-page metadata

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".

Add a pageMetadata block to nooxy/config.js:

pageMetadata: {
  'YOUR_HOME_PAGE_ID': {
    title: 'Ada Lovelace | Mathematician and Writer',
    description: 'Notes, papers, and projects by Ada Lovelace.',
    image: 'https://example.com/og/home.jpg',
    author: 'Ada Lovelace',
  },
  'YOUR_ABOUT_PAGE_ID': {
    title: 'About Ada Lovelace',
    description: 'A short biography, current work, and contact details.',
    author: 'Ada Lovelace',
  },
},
Enter fullscreen mode Exit fullscreen mode

Use the page ID as the key. The image should be an absolute URL, ideally 1200 by 630 pixels. Add your own social image at that URL.

Then run npx nooxy generate and npm run deploy.

Nooxy also exposes seo.keywords and seo.aiAttribution. They are optional and do not replace a clear title, description, and page copy.

4. Submit the sitemap to search engines

For Google:

  1. Open Google Search Console.
  2. Add a property. A Domain property is easiest if your DNS is on Cloudflare.
  3. Verify ownership. Cloudflare makes the DNS verification a copy-paste job.
  4. Go to Sitemaps and submit https://example.com/sitemap.xml.
  5. Use URL Inspection on your homepage and click Request indexing.

For Bing, add the site in Bing Webmaster Tools and submit the same sitemap. You can also import from Google Search Console.

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.

5. Add analytics only if you will use it

Nooxy supports a GA4 measurement ID:

googleTagID: 'G-XXXXXXXXXX',
Enter fullscreen mode Exit fullscreen mode

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.

Updating your site later

There are two loops, and mixing them up causes most of the confusion.

What you changed What to run
Content in Notion Nothing. Publish the page and the site updates, within the cache TTL if you turned on Workers Cache
nooxy/config.js, head.css, head.js, body.js, or header.html npx nooxy generate then npm run deploy
Domain or DNS settings Update them in Cloudflare and the domain field, then redeploy
A new page Add it to slugToPage, then npx nooxy generate and npm run deploy

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.

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.

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.

Troubleshooting

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

The pinned troubleshooting section and the Cloudflare guide's troubleshooting section cover more cases.

The main trade-offs

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.

  • The setup is terminal-first, but it is short. 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.
  • Content is served live from Notion. 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.
  • It publishes public pages, not accounts. 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.
  • The domain is the only likely bill. Hosting, TLS, and the Workers free tier are $0. A custom domain is roughly $10 to $15 per year, and a *.workers.dev URL is free.

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.

Nooxy vs Notion Sites, Super.so, and Fruition

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

A fair summary:

  • Nooxy 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.
  • Notion Sites is the easiest official option. It is click-based and quick to launch; custom domains and advanced SEO are paid.
  • Super.so 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.
  • Fruition helped popularize this approach, but its maintenance has slowed. Check the recent activity before choosing it for a new site.
  • A custom static site gives you maximum control if you enjoy owning the build pipeline.

Nooxy's sweet spot is free hosting, live Notion content, and full control, with a short setup.

Who Nooxy is for

Nooxy is a strong fit if:

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

Choose another tool if:

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

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.

FAQ

Is Nooxy really free?

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 *.workers.dev URL costs nothing.

Do I need a Notion custom domain?

No. You can launch on the free *.workers.dev 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.

Do I need to know how to code?

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.

Can I use Notion databases on my website?

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.

Do content changes appear automatically?

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 nooxy/ are different: run npx nooxy generate and npm run deploy.

Should I turn on Cloudflare caching?

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 configKey, because the cache key does not include the hostname. The Optional extras section has a five-minute setup that leaves browser caching alone.

Is a Nooxy site good for SEO?

A Nooxy site serves your published Notion website on your own domain and gives you the technical pieces: no noindex, 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.

How is this different from Notion Sites and Super.so?

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.

What happens if Notion goes down?

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.

Can I use Cloudflare Pages instead of Workers?

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.

How do I move away later?

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

The 10-minute checklist

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

Final thoughts

Most people do not need a custom CMS. They need a place to write and a boring pipe to the web.

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.

If you try it:

What are you putting online: a portfolio, a lab notebook, or a blog? Tell me in the comments. I read them.


About the author: 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.

GitHub · X · LinkedIn · contact@draphy.org


Sources and further reading

Top comments (0)