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

@pyreon/zero

v0.23.0

Published

Pyreon Zero — zero-config full-stack framework powered by Pyreon and Vite

Readme

@pyreon/zero

Zero-config full-stack meta-framework — file-system routing, SSR/SSG/ISR/SPA, deploy adapters.

@pyreon/zero wraps @pyreon/server + @pyreon/router + @pyreon/head + @pyreon/vite-plugin into a single Vite plugin and a conventions-based project layout: src/routes/ is the route tree ([param], [...catchAll], _layout, _404, _loading, _error, (groups)), per-file export const { loader, meta, middleware, getStaticPaths, revalidate, renderMode } opts into capabilities, and mode: 'ssr' | 'ssg' | 'isr' | 'spa' picks the rendering strategy. Production builds run through one of six deploy adapters (Vercel / Cloudflare Pages / Netlify / Node / Bun / static). The main entry is client-safe; server-only APIs live at @pyreon/zero/server.

Install

bun add @pyreon/zero

sharp is an optional peer dep — install it (bun add -D sharp) only if you use imagePlugin / faviconPlugin / ogImagePlugin.

Quick start

// vite.config.ts
import { defineConfig } from 'vite'
import pyreon from '@pyreon/vite-plugin'
import zero from '@pyreon/zero/server'

export default defineConfig({
  plugins: [
    pyreon({ islands: true }),
    zero({
      mode: 'ssr',             // 'ssr' | 'ssg' | 'isr' | 'spa'
      ssr: { mode: 'stream' }, // 'string' | 'stream'
      adapter: 'node',         // 'vercel' | 'cloudflare' | 'netlify' | 'node' | 'bun' | 'static'
    }),
  ],
})
// src/routes/index.tsx — the homepage
import { useLoaderData } from '@pyreon/router'

export const loader = async () => fetch('/api/hello').then((r) => r.json())

export default function Home() {
  const data = useLoaderData<{ message: string }>()
  return <h1>{data.message}</h1>
}
// src/routes/_layout.tsx — wraps every route in this subtree
import { Link, Meta } from '@pyreon/zero'

export default function Layout({ children }) {
  return (
    <>
      <Meta title="My App" description="..." />
      <nav><Link to="/">Home</Link> <Link to="/posts">Posts</Link></nav>
      <main>{children}</main>
    </>
  )
}

File-system routing

| File | Role | |----------------------------|-------------------------------------------------------------------| | src/routes/index.tsx | / — homepage | | src/routes/about.tsx | /about | | src/routes/[id].tsx | /:id — dynamic param | | src/routes/[...slug].tsx | /* — catch-all | | src/routes/_layout.tsx | Wraps the whole subtree | | src/routes/_404.tsx | Not-found page (auto-emitted as dist/404.html in SSG) | | src/routes/_error.tsx | Route-level error boundary | | src/routes/_loading.tsx | Loader-in-flight component | | src/routes/(group)/x.tsx | /x — group prefix is stripped from the URL | | src/routes/api/*.ts | API routes — export function GET / POST / PUT / DELETE / … |

Each route file may also export loader, meta, middleware, guard, getStaticPaths, revalidate, and renderMode.

Rendering modes

zero({ mode: 'ssr' })  // server-rendered per request (default)
zero({ mode: 'ssg' })  // prerender every static path at build time → dist/<path>/index.html
zero({ mode: 'isr' })  // SSR + in-memory LRU cache, on-demand revalidation
zero({ mode: 'spa' })  // client-only — single dist/index.html shell

Per-route override: export const renderMode = 'ssg'.

SSG

// src/routes/posts/[slug].tsx — enumerate static paths at build time
import type { GetStaticPaths } from '@pyreon/zero/server'

export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
  const posts = await loadAllPosts()
  return posts.map((p) => ({ params: { slug: p.slug } }))
}

export const revalidate = 3600 // optional — build-time ISR (per platform adapter)

export const loader = ({ params }) => fetchPost(params.slug)
export default function Post() { /* ... */ }

SSG features (all on by default; opt out via ssg: { ... }):

  • _404.tsxdist/404.html (per-locale variants if i18n configured)
  • Loader-thrown redirect()dist/_redirects (Netlify/Cloudflare) + _redirects.json (Vercel)
  • Sitemap auto-emit (via seoPlugin({ sitemap: { useSsgPaths: true } }))
  • Concurrent rendering (ssg.concurrency, default 4) + per-path onProgress callbacks
  • Render-error fallback via ssg.onPathError; structured _pyreon-ssg-errors.json artifact
  • Path-collision detection (loud build failure on duplicate URLs)

ISR

zero({ mode: 'isr', isr: { revalidate: 60, maxEntries: 1000 } })

In-memory LRU SSR cache with TTL revalidation. Default keys cache by url.pathname only — for auth-gated pages, supply cacheKey: (req) => … that varies on session cookie / user-id header to avoid serving one user's HTML to another.

Built-in components

import { Image, Link, Script, Meta, Icon, createIcon, createNamedIcon, ThemeToggle } from '@pyreon/zero'

<Image src="/hero.jpg" width={1200} height={600} placeholder="blur" />
<Link to="/about" prefetch="intent">About</Link>
<Script strategy="afterInteractive" src="https://analytics.example.com/script.js" />
<Meta title="..." description="..." />
<Icon as={MyIconSvgComponent} />          {/* loaded via `?component` */}
<ThemeToggle />                            {/* light/dark/system mode */}

<Image> ships with imagePlugin (build-time WebP/AVIF + blur/color placeholders). <Link> is @pyreon/router's RouterLink re-exported. <Meta> writes via @pyreon/head.

Vite plugins (server-only)

import { faviconPlugin, iconsPlugin, ogImagePlugin, seoPlugin, aiPlugin } from '@pyreon/zero/server'

// vite.config.ts
plugins: [
  zero({ /* ... */ }),
  iconsPlugin({
    sets: {
      ui: { dir: './src/icons/ui' },
      brand: { dir: './src/icons/brand', mode: 'image' },
    },
  }),
  faviconPlugin({ source: './src/favicon.svg' }),
  ogImagePlugin({ templates: { default: { /* ... */ } } }),
  seoPlugin({ sitemap: { useSsgPaths: true }, robots: true }),
  aiPlugin(), // generates llms.txt + JSON-LD inference + AI plugin manifest
]

Deploy adapters

import { vercelAdapter, cloudflareAdapter, netlifyAdapter, nodeAdapter, bunAdapter, staticAdapter } from '@pyreon/zero/server'

zero({ adapter: vercelAdapter() })
// or by string id:
zero({ adapter: 'cloudflare' })

Each adapter writes its own platform config (.vercel/output/config.json, _routes.json, netlify.toml, etc.) during closeBundle. Adapters with revalidation support (vercel / cloudflare / netlify) implement Adapter.revalidate(path) — pair with vercelRevalidateHandler for the canonical webhook scaffold.

Server middleware

import { compose } from '@pyreon/zero/server'
import { cspMiddleware, useNonce } from '@pyreon/zero/csp'
import { loggerMiddleware } from '@pyreon/zero/logger'
import { corsMiddleware } from '@pyreon/zero/cors'
import { rateLimitMiddleware } from '@pyreon/zero/rate-limit'
import { compressionMiddleware } from '@pyreon/zero/compression'

const handler = compose([
  loggerMiddleware(),
  corsMiddleware({ origin: 'https://app.example.com' }),
  rateLimitMiddleware({ windowMs: 60_000, max: 100 }),
  cspMiddleware({ directives: { 'script-src': ["'self'", "'nonce-{nonce}'"] } }),
  compressionMiddleware(),
])

i18n routing

zero({
  i18n: { locales: ['en', 'de', 'cs'], defaultLocale: 'en', strategy: 'prefix-except-default' },
})

Routes are duplicated per locale at build time. prefix-except-default keeps the default locale unprefixed (/about) and prefixes others (/de/about); prefix prefixes every locale including the default. Loader context + sitemap hreflang siblings + per-locale _404.tsx all compose automatically.

Subpath exports (server-only)

| Subpath | Notes | |-------------------------------|--------------------------------------------------------------------------------------| | @pyreon/zero/server | createServer, createApp, createISRHandler, adapters, plugins, vercelRevalidateHandler | | @pyreon/zero/client | startClient, hydrateIslands* re-exports | | @pyreon/zero/config | defineConfig, resolveConfig | | @pyreon/zero/env | validateEnv, publicEnv, schema | | @pyreon/zero/middleware | Generic Middleware helpers | | @pyreon/zero/testing | createTestContext, testMiddleware, createTestApiServer |

The main entry (@pyreon/zero) re-exports browser-safe pieces only — components, theme, i18n helpers. Server APIs imported from the main entry throw a clear error pointing at the right subpath.

Gotchas

  • @pyreon/zero@pyreon/zero/server — the main entry is client-safe. Server plugins (faviconPlugin, seoPlugin, createServer) MUST be imported from /server. Importing them from the main entry throws at module-load with a pointer to the right path.
  • ISR with auth-gated pages needs cacheKey: (req) => … that varies on session — the default keys by url.pathname only and will serve one user's HTML to another.
  • _404.tsx rendered HTML is emitted by SSG, but static hosts must be configured to serve it for unmatched URLs (most managed hosts do this by convention; bare S3 / nginx / Caddy need explicit per-locale try_files / [[redirects]]).
  • getStaticPaths / revalidate literal-extraction skips re-exports + non-literal expressions. Inline the value (export const revalidate = 60), don't reference a const.
  • sharp is optional. Without it, imagePlugin falls back to a soft warning in dev and a HARD vite build error in prod (never silently ships an image-broken site).
  • Never pass layout to startClient when using fs-router's _layout.tsx convention — the route tree already wraps every page in the layout, and the explicit option double-mounts.

Documentation

Full docs: docs.pyreon.dev/docs/zero (or docs/docs/zero.md in this repo).

SSG-specific guide: docs.pyreon.dev/docs/ssg.

License

MIT