npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nooxy

v2.0.0

Published

Zero dependency Notion reverse proxy for custom domains with seamless in-domain navigation for inner Notion pages, full HTML/CSS/JS customization, multi-instance support, CLI tools, and local development support. Built for Cloudflare Workers and modern No

Readme

Nooxy


The Problem

You built something great in Notion. Now you want to share it with the world on your own domain.

Your options today:

| Solution | Cost | SEO | Customization | Interactivity | | ---------------- | --------- | --------------------------------------- | ------------- | ------------- | | Notion Sites | $10-22/mo | Limited, noindex issues | Minimal | Full | | Super.so | $12-28/mo | Good, but subdomain-only hurts rankings | Good | Lost (static) | | Fruition | Free | Poor, outdated | Limited | Full | | Nooxy | Free | Full SEO suite | Complete | Full |

Notion Sites charges $10/month per domain with limited SEO and customization. Super.so costs $12-28/month and converts your pages to static HTML—you lose Notion's live databases, filtering, and real-time updates. Fruition is no longer maintained and lacks modern SEO features.

Nooxy gives you everything. For free.

See It Live

These sites run on Nooxy right now:

View source, check the SEO tags, test the interactivity. It works.

Using Nooxy? Share your site in Discussions — we'd love to see it!


Why Nooxy?

What you get

  • $0/month — No subscriptions, no limits
  • Full SEO — Canonical URLs, structured data, proper indexing
  • Complete control — Inject any CSS, JavaScript, HTML
  • Live Notion — Real-time databases, filtering, collaboration
  • Your domain — Professional URLs like yourdomain.com/about

What you avoid

  • ~~$120-336/year~~ on hosting fees
  • ~~Subdomain SEO penalties~~ from free tiers
  • ~~Static pages~~ that lose Notion's power
  • ~~Vendor lock-in~~ from closed platforms
  • ~~Outdated tools~~ that break with Notion updates

Features

SEO That Actually Works

Nooxy rewrites Notion's HTML to give search engines exactly what they need:

  • Removes noindex tags — Your pages get indexed by Google
  • Canonical URLs — https://yourdomain.com/about not /About-abc123def
  • Structured data — JSON-LD schema for rich search results
  • Open Graph & Twitter Cards — Beautiful social media previews
  • Custom meta tags — Title, description, keywords, author per page
  • AI attribution — Proper source credits for ChatGPT, Claude, Perplexity
  • XML sitemap — Auto-generated at /sitemap.xml
  • Robots.txt — Proper crawler directives at /robots.txt

Complete Customization

  • Custom CSS — Override any Notion style, add your brand
  • JavaScript injection — Analytics, interactions, custom functionality
  • HTML headers — Navigation bars, announcements, CTAs
  • Google Fonts — Apply any font family site-wide
  • Google Analytics — Built-in GA4 support

Production Ready

  • Zero dependencies — Nothing to break, nothing to update
  • Edge computing — Runs on Cloudflare Workers' global network
  • Node.js support — Works with any modern Node.js runtime (22+)
  • Multi-tenant — Host multiple sites from one deployment
  • Local development — Test locally before deploying

Developer Experience

  • TypeScript — Full type safety and IntelliSense
  • CLI tools — npx nooxy init and npx nooxy generate
  • Auto-minification — CSS, JS, HTML optimized automatically
  • Clean URLs — /about instead of /About-Page-abc123def456

Quick Start

New to this? If you don't have a project set up yet, start with our Full Deployment Guides instead — they walk you through everything from creating an account to deploying your live site. Currently available for Cloudflare Workers (recommended, ~15 min).

This section shows how to add Nooxy to an existing JavaScript/TypeScript project. It works with Cloudflare Workers, Node.js, Deno, Bun, or any runtime that supports the Fetch API.

For all configuration options and customization, see:

1. Install

In your project folder (where package.json is), run:

npm install nooxy

2. Initialize

npx nooxy init

This creates a nooxy/ folder with all configuration files:

nooxy/
├── config.js       # Main configuration
├── head.css        # Custom CSS (optional)
├── head.js         # JavaScript for <head> (optional)
├── body.js         # JavaScript for <body> (optional)
└── header.html     # Custom header HTML (optional)

3. Get Your Notion Page IDs

Every Notion page has a unique Page ID — a 32-character code that identifies it.

How to find it:

  1. Open your Notion page in a browser
  2. Look at the URL:
    https://www.notion.so/My-Page-Title-abc123def456789012345678901234ab
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                         This is your Page ID (32 characters)
  3. Copy just the ID part (after the last hyphen)

Examples:

| URL | Page ID | | --------------------------------------------------------------- | ---------------------------------- | | notion.so/Home-abc123def456789012345678901234ab | abc123def456789012345678901234ab | | myworkspace.notion.site/Blog-11122233344455566677788899900aaa | 11122233344455566677788899900aaa |

Important: Make sure your Notion pages are published to web (Share → Publish → Publish to web).

4. Configure

Edit nooxy/config.js:

export const SITE_CONFIG = {
  // Your custom domain (without https://)
  domain: 'yourdomain.com',

  // Your Notion workspace domain
  // Find it in your Notion URL: https://YOUR-WORKSPACE.notion.site/...
  notionDomain: 'yourworkspace.notion.site',

  // Site name (appears in browser tabs and search results)
  siteName: 'Your Site Name',

  // Map URL paths to Notion page IDs
  // Left side: URL on your site
  // Right side: Notion page ID (32 characters)
  slugToPage: {
    '/': 'YOUR_HOME_PAGE_ID', // yourdomain.com/
    '/about': 'YOUR_ABOUT_PAGE_ID', // yourdomain.com/about
    '/blog': 'YOUR_BLOG_PAGE_ID', // yourdomain.com/blog
  },

  // SEO settings (optional but recommended)
  seo: {
    indexing: true, // Allow search engines to index
    keywords: 'your, keywords, here',
    defaultAuthor: 'Your Name',
  },

  // These are auto-generated — don't modify
  customHeadCSS: HEAD_CSS_STRING,
  customHeadJS: HEAD_JS_STRING,
  customBodyJS: BODY_JS_STRING,
  customHeader: HEADER_HTML_STRING,
};

5. Generate

After editing your config, process the files:

npx nooxy generate

Run this command every time you change anything in the nooxy/ folder.

6. Integrate with Your Runtime

Nooxy exports a single function that handles all requests. Add this to your server/worker entry point:

Need step-by-step deployment instructions? See the Full Deployment Guides for complete setup including file creation, testing, and deployment.

Cloudflare Workers:

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

Node.js (22+):

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

const proxy = initializeNooxy(SITE_CONFIG);

// Most hosts inject the port they expect you to bind.
const PORT = Number(process.env.PORT ?? 8787);

http
  .createServer(async (req, res) => {
    const url = `http://${req.headers.host}${req.url}`;

    // Notion loads page content over POST /api/v3/..., so the body has to be
    // forwarded or the page renders an empty shell. Node requires duplex: 'half'
    // whenever the body is a stream.
    const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
    const request = new Request(url, {
      method: req.method,
      headers: req.headers,
      body: hasBody ? req : undefined,
      duplex: hasBody ? 'half' : undefined,
    });

    const response = await proxy(request);

    res.statusCode = response.status;

    // Set-Cookie must be written as separate headers. headers.forEach() yields it
    // once with the values comma-joined, and repeated setHeader() calls overwrite
    // each other, so signing in would lose every cookie but the last.
    const cookies = response.headers.getSetCookie();
    if (cookies.length > 0) {
      res.setHeader('set-cookie', cookies);
    }
    response.headers.forEach((value, key) => {
      if (key.toLowerCase() !== 'set-cookie') {
        res.setHeader(key, value);
      }
    });

    res.end(Buffer.from(await response.arrayBuffer()));
  })
  .listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}`);
  });

Any Fetch-compatible runtime:

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

const proxy = initializeNooxy(SITE_CONFIG);

// proxy(request: Request) => Promise<Response>
// Pass any standard Request, get a standard Response

Full Deployment Guides

The Quick Start above covers Nooxy setup. If you need a complete walkthrough — from creating an account to deploying your live site — use these platform-specific guides:

| Platform | Guide | Description | | ---------------------- | ----------------------------------------------- | ---------------------------------------------------------- | | Cloudflare Workers | Full Guide → | Recommended. Free tier, global edge network, ~15 min setup | | Node.js | Coming soon | For self-hosted servers |

These guides include all the Nooxy setup steps plus platform-specific deployment instructions.


Configuration Reference

All configuration is in nooxy/config.js. After any changes, run npx nooxy generate to apply them.

Required Fields

| Field | Description | Example | | -------------- | ------------------------------------------------------------------------- | ---------------------- | | domain | Your custom domain (without https://) | example.com | | notionDomain | Your Notion workspace domain (prevents serving unintended Notion content) | myname.notion.site | | siteName | Site name for browser tabs, SEO, and og:site_name | My Portfolio | | slugToPage | URL path → Notion page ID mapping (32-char hex IDs) | { '/': 'abc123...' } |

Generated Fields

These fields are auto-populated by npx nooxy generate from files in the nooxy/ folder. Don't edit them directly — edit the source files instead:

| Field | Source File | Purpose | | --------------- | ------------------- | ------------------------------------------------------------ | | customHeadCSS | nooxy/head.css | CSS injected into <head> | | customHeadJS | nooxy/head.js | JavaScript injected into <head> (runs before page loads) | | customBodyJS | nooxy/body.js | JavaScript injected before </body> (runs after page loads) | | customHeader | nooxy/header.html | HTML injected into page header (navigation, banners, etc.) |

SEO Configuration

seo: {
  // Enable search engine indexing (default: true)
  // When true: removes Notion's noindex tags, adds canonical URLs,
  // injects robots meta, generates sitemap.xml and robots.txt
  indexing: true,

  // Canonical domain (if different from domain)
  // Use when Nooxy runs on a subdomain but SEO should point to main domain
  // Affects: canonical URLs, og:url, twitter:url, sitemap.xml
  canonicalDomain: 'example.com',

  // Path mapping for canonical URLs
  // Maps paths from your Nooxy domain to the canonical domain
  canonicalPathMap: {
    '/': '/home',              // subdomain.example.com/ → example.com/home
    '/docs': '/documentation',
  },

  // Meta keywords (adds <meta name="keywords">)
  keywords: 'notion, website, portfolio',

  // Default author for all pages (adds <meta name="author"> and article:author)
  // Can be overridden per-page in pageMetadata
  defaultAuthor: 'Your Name',

  // Replace "Notion" branding with your brand in all meta tags
  // Affects: <title>, og:title, og:description, og:site_name, twitter:title, etc.
  // Default: uses siteName
  brandReplacement: 'Your Brand',

  // AI crawler attribution (ChatGPT, Claude, Perplexity)
  // Adds: <meta name="ai:source_url"> and <meta name="ai:source_attribution">
  // Helps AI systems properly credit your content
  aiAttribution: 'Your Name - yourdomain.com',
}

Page-Specific Metadata

Override meta tags for specific pages. Key is the Notion page ID (32 characters):

pageMetadata: {
  'abc123def456789012345678901234ab': {
    title: 'Custom Page Title',           // <title>, og:title, twitter:title
    description: 'Custom meta description', // meta description, og:description, twitter:description
    image: 'https://yourdomain.com/og.jpg', // og:image, twitter:image
    author: 'Page Author Name',            // article:author (overrides seo.defaultAuthor)
  },
}

Social & Branding

| Field | Description | Example | | --------------- | ------------------------------------------------------------------- | --------------------------------- | | twitterHandle | Twitter/X handle for twitter:site meta tag (include @) | @yourusername | | siteIcon | Custom favicon URL (.ico format). If not set, uses Notion's default | https://example.com/favicon.ico |

The icon is fetched by your server, so a few limits apply. Any of these falls back to Notion's favicon and logs the reason — your site keeps working either way:

  • must be http:// or https://
  • must not be a loopback or link-local address (localhost, 127.x.x.x, ::1, 169.254.x.x) — a private LAN address such as 10.0.0.5 is fine, so serving the icon from another container works
  • must respond within 3 seconds and be under 512 KB

Typography & Analytics

| Field | Description | Example | | ------------- | -------------------------------------------------------------------------------------------- | ----------------- | | googleFont | Google Font family name from fonts.google.com. Applied site-wide | Inter, Roboto | | googleTagID | Google Analytics 4 measurement ID. Injects GA4 tracking script | G-XXXXXXXXXX |

404 Page

Custom Notion page to display for 404 errors:

fof: {
  page: 'NOTION_404_PAGE_ID',  // Your custom 404 page (32-char ID)
  slug: '/404',                // URL path (default: '/404')
}

Subdomain Redirects

Redirect subdomains to your main domain. Common use: redirect www to non-www:

subDomains: {
  www: {
    redirect: 'https://example.com',  // www.example.com → example.com (301 redirect)
  },
}

Nooxy Configuration

nooxy: {
  // Show "Made with Nooxy" badge in header (default: true)
  // Set to false to hide it... 💔 it'll break my heart, but hey,
  // if it helps your site look cleaner, I'll survive... probably 😢
  showBadge: true,
}

💜 If Nooxy helped you, a GitHub sponsorship would mean the world!

Auto-Generated Features

These features work automatically — no configuration needed:

| Feature | URL | Description | | ------------------ | -------------- | -------------------------------------------------------------- | | Sitemap | /sitemap.xml | Auto-generated XML sitemap with all pages from slugToPage | | Robots.txt | /robots.txt | Points crawlers to your sitemap | | Clean URLs | — | Rewrites Notion URLs (/Page-abc123) to your slugs (/about) | | JSON-LD Schema | — | Injects structured data for rich search results | | Canonical URLs | — | Adds <link rel="canonical"> to every page | | Open Graph | — | Rewrites og:url, og:site_name for proper social sharing | | Twitter Cards | — | Rewrites twitter:url, twitter:site for Twitter/X previews |


CLI Commands

# Create nooxy/ folder with config files
npx nooxy init

# Process config changes (run after editing any nooxy/ file)
npx nooxy generate

# Generate with custom path
npx nooxy generate --path=./my-project

# Generate without minification (for debugging)
npx nooxy generate --no-minify

# Print the installed version (useful in bug reports)
npx nooxy --version

Hosting Several Sites From One Deployment

One worker can serve any number of Notion sites. Each one needs its own configKey, which is what keeps its config separate:

import { initializeNooxy } from 'nooxy';
import { SITE_CONFIG as SITE_A } from '../nooxy-a/config';
import { SITE_CONFIG as SITE_B } from '../nooxy-b/config';

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

export default {
  async fetch(request: Request): Promise<Response> {
    const host = new URL(request.url).hostname;

    return host.endsWith('b.example') ? siteB(request) : siteA(request);
  },
};

Config is processed once and cached per key. If you omit configKey, the site's domain is used as the key, so two sites on different domains stay separate anyway — but passing an explicit key is clearer, and it is required if two sites share a domain. Reusing one key for two different configs logs a warning and keeps the first, since silently serving one site's domain for another is worse.


Project Files

When you run npx nooxy init, it creates a nooxy/ folder with these files:

nooxy/
├── config.js       # Main configuration (domain, pages, SEO settings)
├── head.css        # Custom CSS injected into <head>
├── head.js         # JavaScript injected into <head> (runs before page loads)
├── body.js         # JavaScript injected before </body> (runs after page loads)
└── header.html     # Custom HTML injected into page header

How Files Are Processed

  1. You edit the source files (head.css, body.js, etc.)
  2. Run npx nooxy generate — this reads the files, minifies them, and writes them to nooxy/generated/ (which config.js imports)
  3. When your site runs, Nooxy injects these into every page response
                     npx nooxy generate
+---------------+                          +---------------+
|   head.css    |  ----------------------> |               |
|   head.js     |    (minifies & embeds)   |   config.js   |
|   body.js     |  ----------------------> |               |
|  header.html  |                          |               |
+---------------+                          +---------------+

Important: Run npx nooxy generate every time you change anything in the nooxy/ folder. Your changes won't take effect until you regenerate and redeploy.

File Reference

config.js — Main Configuration

Contains all your site settings. See Configuration Reference for all options.

export const SITE_CONFIG = {
  domain: 'yourdomain.com',
  notionDomain: 'yourworkspace.notion.site',
  siteName: 'Your Site Name',
  slugToPage: {
    '/': 'YOUR_PAGE_ID',
  },
  // ... other options
};

head.css — Custom Styles

CSS injected into <head>. Use this to override Notion's default styles:

/* Hide Notion's top bar */
.notion-topbar {
  display: none !important;
}

/* Custom page styling */
.notion-page-content {
  max-width: 900px;
  margin: 0 auto;
}

/* Dark mode support */
.dark .notion-page-content {
  background: #1a1a1a;
}

Tips:

  • Use !important to override Notion's styles
  • Notion uses .dark class for dark mode
  • Inspect your Notion page to find class names to target

head.js — Early JavaScript

JavaScript injected into <head>. Runs before the page content loads. Use for:

  • Analytics that need to run early
  • Setting up global variables
  • Theme detection before render
// Example: Set theme before page renders to prevent flash
const theme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', theme);

body.js — Main JavaScript

JavaScript injected before </body>. Runs after the page content loads. Use for:

  • DOM manipulation
  • Event listeners
  • Interactive features
document.addEventListener('DOMContentLoaded', () => {
  // Add smooth scrolling
  document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
    anchor.addEventListener('click', (e) => {
      e.preventDefault();
      document.querySelector(anchor.getAttribute('href')).scrollIntoView({
        behavior: 'smooth',
      });
    });
  });
});

header.html — Custom Header

HTML injected at the top of the page body. Use for navigation bars, announcements, or banners:

<nav style="padding: 1rem; background: #f5f5f5; display: flex; gap: 1rem;">
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/blog">Blog</a>
  <a href="/contact">Contact</a>
</nav>

After Making Changes

Every time you edit any file in nooxy/:

# 1. Regenerate the config
npx nooxy generate

# 2. Redeploy (platform-specific)
npm run deploy          # Cloudflare Workers
# or restart your server  # Node.js

Content changes in Notion appear automatically — no regeneration needed. Only changes to nooxy/ files require regeneration.


How It Works

User Request --> Nooxy --> Notion
                  |          |
                  |    <-----+  (fetches HTML)
                  |
                  v
            [Rewrite & Inject]
                  |
                  v
User Response <-- Modified HTML
  1. Intercepts requests to your custom domain
  2. Maps clean URLs (/about) to Notion page IDs
  3. Fetches content from Notion's servers
  4. Rewrites meta tags, URLs, and structured data for SEO
  5. Injects your custom CSS, JavaScript, and headers
  6. Serves the optimized response to visitors

Compared to Alternatives

vs Notion Sites

| | Notion Sites | Nooxy | | -------------------- | ------------ | --------- | | Price | $10-22/mo | Free | | Custom domain | Paid add-on | Included | | SEO control | Limited | Full | | CSS/JS injection | No | Yes | | noindex removal | Manual | Automatic |

vs Super.so

| | Super.so | Nooxy | | ------------------------ | ------------- | --------- | | Price | $12-28/mo | Free | | Notion interactivity | Lost (static) | Preserved | | Database filtering | No | Yes | | Real-time updates | No | Yes |

vs Fruition

| | Fruition | Nooxy | | ---------------- | --------- | ------------- | | Maintained | No (2020) | Yes (2024+) | | SEO features | Basic | Comprehensive | | TypeScript | No | Yes | | CLI tools | No | Yes |


Troubleshooting

| Problem | Solution | | -------------------------------- | ------------------------------------------------------------------------------------------------------- | | Pages return 404 | Verify page IDs are 32 characters, pages are published in Notion, notionDomain matches your workspace | | CSS not applied | Run npx nooxy generate after changes, use !important to override Notion styles | | SEO tags not appearing | Ensure seo.indexing is true, view page source (not rendered DOM), redeploy | | "Cannot find module 'nooxy'" | Run npm install nooxy |


Support


Contributing

See CONTRIBUTING.md for guidelines.


License

MIT License - see LICENSE for details.


Trademark

Nooxy and the Nooxy badge are trademarks of David Raphi. The MIT license covers the source code, not the name.

Forks and derivative works are welcome. Please give yours a different name, and do not present it in a way that suggests it is Nooxy or endorsed by the project. See TRADEMARK.md for what is and is not allowed.


Acknowledgments

Inspired by Fruition and NoteHost, rebuilt for the modern web with comprehensive SEO, TypeScript, and zero dependencies.


Made with ❤️ by David Raphi

GitHub X LinkedIn


Stop paying for Notion hosting. Start with Nooxy.

npm install nooxy && npx nooxy init

Star this repo if it saved you $120+/year.