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

@gyral/ssr

v0.3.6

Published

Gyral server rendering: Declarative Shadow DOM pages with hydration seeds.

Readme

@gyral/ssr

Gyral server rendering: whole pages of Gyral components rendered on the server (shadow components as Declarative Shadow DOM, light components as plain children), with hydration seeds so the browser picks up where the server left off. Rendering uses @gyral/core's server renderer: synchronous, no DOM, no Node-only APIs. Works in any runtime with the Fetch API Response (Node, Hono, Deno, workers).

Install

pnpm add @gyral/ssr @gyral/core

Hydration is built into @gyral/core (view/07-hydration.md): the client entry imports the components and each one adopts the server's DOM in place.

Example

// server.ts
import { html } from '@gyral/core';
import { renderPage } from '@gyral/ssr';
import './app.js'; // defines <my-app>

export const handle = (req: Request): Response =>
  renderPage({
    title: 'My app',
    body: html`<my-app path=${new URL(req.url).pathname}></my-app>`,
    scripts: ['/client.js'],
  });

// client.ts: hydration is built into @gyral/core
import './app.js';

The head fields (title, description, canonical, robots, meta, links, jsonLd, lang, dir) are a Head from @gyral/core: build it with one pure function and pass the same value to the router's setHead() after client navigations, so both write the same head. Anything else for the head goes in extraHead (core's html).

renderPage writes the status and headers first, then the body in chunks pulled from a synchronous render, one component boundary per pull (a slow reader slows the render, a cancelled body stops it). Load data before rendering: there is no async or suspense streaming, so the head can't go out while data loads.

renderPage({ …, csp: { directives } }) sets a Content-Security-Policy whose style-src allows every component's <style> and the page's styles by hash, built when the page renders, so components imported late are covered. contentSecurityPolicy({ styles, directives }) returns the same header ahead of time, for the components registered when it is called (development warns if a registered component's hash is missing from a header passed to renderPage). Add styleAttributes: 'hash' to also allow the page's style attributes by hash (style-src-attr 'unsafe-hashes'), so they apply on first paint; the page is then rendered to a string before the response is built. formAction handles no-JS form posts with the same schema as the browser.

@gyral/ssr/static prerenders pages to static files (SSG) and serves built apps in production (prerender, productionServer). Its clientAssetsFromManifest() reads Vite's manifest for the entry URL and the chunks to preload (its imports and the lazily loaded hydration chunk); pass them as renderPage({ scripts: [entry], modulepreload }) so server-rendered pages hydrate without extra round trips. CSS imported by the client entry comes back hashed as css: pass it as renderPage({ stylesheets }) for <link rel="stylesheet"> elements (served immutable, allowed by style-src 'self'). A route whose module is imported lazily adds it with clientAssetsFromManifest(manifestPath, entry, also), or assets(modules) from productionServer's createApp options ({ modulepreload, stylesheets } to spread into renderPage).

productionServer serves /assets/* from assetsDir (default dist/client/assets) through assetHandler: GET and HEAD, immutable caching, a content-type per file type, content-length, nosniff, traversal and dot-path refusal, a 400 for malformed escapes and no-store 404s, so a CDN never keeps a miss. Files stay in memory (bounded; cache: false to read from disk). Apps with no prerendered pages pass staticDir: false. assetHandler({ dir, prefix, cache }) also works alone, for example for a volume that keeps every release's hashed files so tabs opened before a deploy still load theirs.

@gyral/ssr/node mounts any fetch handler on node:http: createServer(toNodeListener(app.fetch, { origin })). Request bodies stream in, response bodies stream out with backpressure, a client that disconnects aborts request.signal and cancels the body, HEAD sends headers only and each set-cookie stays separate. The handler also receives { incoming, remoteAddress } (the Node request and the client's address; Hono's getConnInfo reads it as c.env), and productionServer passes it on to your app.

// server/prod.ts
import { createServer } from 'node:http';
import { toNodeListener } from '@gyral/ssr/node';
import { productionServer } from '@gyral/ssr/static';
import { createApp } from './app.js'; // ({ clientEntry, modulepreload, stylesheets }) => { fetch }

const app = await productionServer({ distDir: 'dist', createApp });
createServer(toNodeListener(app.fetch, { origin: 'https://example.com' })).listen(3000);

Documentation

Guides and API reference: gyral.dev. Source, issues and the consumer setup guide (packages, Vite preset, server rendering): github.com/gyraljs/gyral.

Status: 0.x, on npm. Breaking changes to the public API come only in a new minor version, with a migration guide.

License

MIT © Mike Zupper. See LICENSE and NOTICE (Cycle.js attribution). Gyral, gyraljs and the Gyral logo are trademarks of Mike Zupper.