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

@qewordly/react

v0.7.0

Published

Headless-first React SDK for Qewordly blogs. Server Components by default, isolated client islands for engagement.

Readme

@qewordly/react

Headless-first blog SDK. Data functions run on the server (RSC, SSG, Route Handlers); components render to static HTML with zero client JS.

5-minute integration (Next.js App Router)

// lib/qw.ts
import { createQewordlyClient } from "@qewordly/react";

export const API_URL = process.env.QEWORLDLY_API_URL!; // e.g. https://api.qewordly.com
export const qw = createQewordlyClient({
  baseUrl: API_URL,
  apiKey: process.env.QEWORLDLY_API_KEY!, // qw_live_… - server env only
});
// app/blog/[slug]/page.tsx
import { QewordlyPost, QewordlyJsonLd, getPostJsonLd, getPostMetadata } from "@qewordly/react";
import { qw, API_URL } from "@/lib/qw";
import { notFound } from "next/navigation";

// imageBaseUrl is the API origin: relative /uploads/… cover paths resolve
// against it. Without it they render against the site host and break.
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const post = await qw.getPost((await params).slug);
  if (!post) return {};
  const meta = getPostMetadata(post, { imageBaseUrl: API_URL });
  return { title: meta.title, description: meta.description };
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const post = await qw.getPost((await params).slug, { revalidate: 60 });
  if (!post) notFound();
  const url = `https://yoursite.com/blog/${post.slug}`;
  return (
    <>
      {/* shareUrl renders the share row (X/LinkedIn/Facebook/email intents).
          Omit it and no row renders - e.g. unpublished previews. */}
      <QewordlyPost post={post} imageBaseUrl={API_URL} shareUrl={url} />
      <QewordlyJsonLd data={getPostJsonLd(post, { url, imageBaseUrl: API_URL })} />
    </>
  );
}
// app/sitemap.ts
import { qw } from "@/lib/qw";

export default async function sitemap() {
  const entries = await qw.getSitemapEntries();
  return entries.map((e) => ({
    url: `https://yoursite.com/blog/${e.slug}`,
    lastModified: new Date(e.updatedAt),
  }));
}

Rules

  • Keys stay server-side - except publishable keys. Never prefix qw_live_ with NEXT_PUBLIC_, never fetch reads from the browser. There are no client hooks in the read client - that is deliberate, not missing. The one exception is QewordlyActionBar (reactions/share menu, "use client"): it runs on a qw_pub_ publishable key, which is embed-safe by design (tenant + engage scope only, revocable) - see Engagement below.
  • Theme: qw.getTheme() + <QewordlyStyles theme={theme} /> injects --qw-* variables. QewordlyPost/QewordlyHeader ship a designed default (title block, meta, typography) skinned by those variables - override via className (.qw-post, .qw-post-body, .qw-header, .qw-nav-link, .qw-cta, .qw-actionbar, .qw-share, .qw-share-menu hooks) or showHeader={false}/renderNav for full control.
  • Preview: issue a token (POST /workspace/preview-token), enable Next draft mode, pass previewToken to any read. Drafts resolve; archived never does.
  • Caching: pass revalidate / tags to map onto Next fetch semantics. Publish webhooks (US-094) will call your revalidateTag endpoint for instant updates.
  • Eject anytime: every component is a thin wrapper - qw.getPosts() returns plain data you can render however you like.

Engagement (reactions + share menu)

// Server component: render the bar with the post. Reactions activate when
// `engagement` carries your publishable key (NEXT_PUBLIC_QEWORLDLY_PUBLIC_KEY
// is safe - qw_pub_ keys only resolve to tenant + engage scope).
import { QewordlyActionBar } from "@qewordly/react";

<QewordlyActionBar
  slug={post.slug}
  shareUrl={url}
  engagement={{ baseUrl: API_URL, publicKey: process.env.NEXT_PUBLIC_QEWORLDLY_PUBLIC_KEY! }}
/>;

A post renders two bars (header + footer). They stay in sync through QewordlyEngagementProvider — QewordlyPost wraps itself automatically; hosts composing bars manually should wrap once per post. A standalone bar outside any provider owns its own state (same behavior, one extra fetch).

// Or drive your own UI (framework-free, works anywhere fetch exists):
import { createQewordlyEngagement } from "@qewordly/react";

const engage = createQewordlyEngagement({ baseUrl: API_URL, publicKey: process.env.NEXT_PUBLIC_QEWORLDLY_PUBLIC_KEY! });
await engage.getReactions(slug); // { slug, count, reacted }
await engage.setReaction(slug, true); // explicit state - idempotent, retries converge
  • Issue the key in dashboard Settings → API Keys → Publishable (shown once, like all keys).
  • Omit engagement and the reaction button renders disabled - share menu and comments are unaffected. Draft previews never show live counts.

Responses (comments)

// Server component: approve-only thread (never leaks pending rows).
import { QewordlyCommentList, QewordlyCommentForm } from "@qewordly/react";

const comments = await createQewordlyComments({
  baseUrl: API_URL,
  publicKey: process.env.NEXT_PUBLIC_QEWORLDLY_PUBLIC_KEY!,
}).getComments(post.slug);

<QewordlyCommentList comments={comments} />
<QewordlyCommentForm
  slug={post.slug}
  engagement={{ baseUrl: API_URL, publicKey: process.env.NEXT_PUBLIC_QEWORLDLY_PUBLIC_KEY! }}
/>;

Or let the post render the whole thread: pass comments (+ commentEngagement for the form) to QewordlyPost - the bar count derives from the thread automatically.

  • Submits always land pending; the form says so honestly. No emails, no accounts.
  • Bodies render as plain text - guest HTML can never execute.
  • The website honeypot is server-enforced; bots get a fake success.