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

@ramonda/server

v0.2.2

Published

The plumbing a Ramonda server render needs — a DOM for the render, and the document it goes into — so an app does not hand-roll it.

Readme

@ramonda/server

The plumbing a Ramonda server render needs: a DOM to render into, and the document the render goes into.

npm license

Status: 0.x. The API changes freely between releases while the design is being explored; from 1.0 the interfaces hold. See the root README.

npm install @ramonda/server

Documentation: ramonda.dev/ssr/server

It brings its own DOM — you do not install one. That is deliberate: linkedom was a peer once, an app put it in devDependencies, and a production install produced a server that started and then died on ERR_MODULE_NOT_FOUND.

import { fillDocument, installDom, mimeFor, parseCookies } from "@ramonda/server";

const dom = installDom(`http://localhost:5173${req.url}`);
try {
  const { html, title, head } = await render({ cookies: parseCookies(req.headers.cookie) });
  res.end(fillDocument({ template, html, title, head }));
} finally {
  dom.close();
}

Routing is not here. The route plan, the ISR cache and the render modes are @ramonda/router/server; this package knows nothing about routes.

Why it exists

Every SSR app grows its own copy of these, and the copies drift. In this repository, three of them did:

  • One project's server.mjs and its scripts/prerender.mjs each had a DOM installer. The server was moved from jsdom to linkedom; the prerender step was not. The build bundled successfully and then died at prerender with ERR_MODULE_NOT_FOUND.
  • An unescaped <title> and a $-sequence corruption were found and fixed in one copy of the shell fill. The scaffolded template shipped both for months afterwards.

A fix that has to be applied by hand to every app reaches one of them.

installDom(url)

A fresh DOM on globalThis, seeded at the request URL — which is also how the router learns the page. Returns a handle:

const dom = installDom("http://localhost:5173/users/42");
// … render …
dom.close();

A handle rather than the DOM. Two implementations do not agree on what a window is, and a caller reaching past this into dom.window.close() — jsdom's shape, not linkedom's — is what broke every ISR and dynamic render once already.

linkedom, not jsdom. It needs no Node built-in, which is what lets the same render run on an edge runtime, and it builds a document in 0.018 ms against jsdom's 4.814 — a cost paid on every request. Measured on a 30-row page against a production build: 0.662 ms per request against 8.530 ms, and end to end on a live dynamic route, 2.97 ms against 9.49 ms.

installWindow(url, window, options?)

The globals half, for a DOM you built yourself.

const dom = new JSDOM(shell, { url });
installWindow(url, dom.window, { navigation: "dom" });

navigation decides whose location and history the render sees, and a DOM that has neither is refused rather than rendered against undefined. "request", the default, builds both from url: the DOM has none, and each render is one request at one URL. "dom" takes the DOM's own pair — jsdom's pushState moves its location, which is how a sequential prerender walks a whole site on a single document instead of building one per page.

It is an argument rather than something detected, because detecting it does not work: linkedom's window falls through to globalThis for anything it does not define, so the window built for the second request reports the first request's location as its own.

fillDocument({ template, html, title, head })

The render, put into the shell — <!--ssr--> for the app and <!--head--> for its head.

fillDocument({ template, html, title: document.title, head });

Every substitution is a function replacement. String.prototype.replace reads $&, $`, $', $$ and $1 in the replacement as patterns, so a page rendering a price — "Save $$ today" — put the marker back into its own output and still answered 200.

The title is escaped and the head is not. head is outerHTML serialised from real nodes, so it is already markup. title is raw text read back from document.title, so a page that titles itself from a product name or a search term decides what markup lands in the document.

A shell missing a marker is returned as it is: a server that throws answers 500 on every route, and a page with no app in it is the more diagnosable of the two.

parseCookies(header) and mimeFor(path)

parseCookies reads a Cookie header into the Map a request context wants. It splits on the first = only, because base64 pads with =, and it keeps a value raw rather than throwing when it is not valid percent-encoding — one visitor with a malformed cookie should not take their request down.

mimeFor answers the content type for a built asset, and application/octet-stream for anything it does not know. A wrong Content-Type is worse than none, since a browser acts on it.

License

MIT