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

@roottale/cms-renderer-next

v0.25.0

Published

RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites. Companion of @roottale/cms-renderer-astro (ADR-0034 §1.5 amended).

Readme

@roottale/cms-renderer-next

RootTale CMS public-render React Server Components for external customer sites.

Drop-in <RootTaleBlogList> / <RootTaleBlogPost> / <RootTaleLeadForm> for Next.js App Router, server components elsewhere. SSR-only — no 'use client' boundary, no API key in the browser bundle.

Pair with @roottale/cms-client (the fetch client) and the RootTale CMS admin (mysite.roottale.com) where you author the content.

Astro counterpart: @roottale/cms-renderer-astro — equivalent surface (ADR-0034 §1.5).

Install

npm install @roottale/cms-renderer-next @roottale/cms-client
# or
pnpm add @roottale/cms-renderer-next @roottale/cms-client

Peer dep: react@^19.

Setup

  1. Grab your API key from the admin (Settings → API keys → "발급"). Format: rtlk_cust_*. Server-side only.
  2. Store it as a server env var:
# .env.local
ROOTTALE_API_KEY=rtlk_cust_xxxxxxxxxxxxxxxxxx
# optional — override the default https://api.roottale.com
ROOTTALE_API_BASE=https://api.roottale.com
  1. Import the scoped CSS once (root layout):
// app/layout.tsx
import "@roottale/cms-renderer-next/styles";

The CSS is scoped under [data-roottale-cms] and uses :where() so customer styles win without !important. Every --rt-* variable has a static fallback.

Revalidation webhook

For near-real-time blog updates, expose POST /api/revalidate on the customer site and register that URL in ADMIN. ISR settings such as revalidate = 1800 are fallback only.

// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";

import { createRevalidateRoute } from "@roottale/cms-renderer-next/routes";

export const POST = createRevalidateRoute({
  apiKey: process.env.ROOTTALE_API_KEY!,
  apiBase: process.env.ROOTTALE_API_BASE,
  revalidate: revalidatePath,
});

ADMIN setup: open /s/{tenant-slug}/sites/{site-id}, set Webhook URL to https://<customer-domain>/api/revalidate, keep it enabled, and save. See docs/cms-revalidation-webhooks.md for the full operational contract.

Blog list — app/blog/page.tsx

import { RootTaleBlogList } from "@roottale/cms-renderer-next/server";

export default function BlogPage() {
  return (
    <main>
      <h1>Blog</h1>
      <RootTaleBlogList
        apiKey={process.env.ROOTTALE_API_KEY!}
        limit={20}
        showCategoryFilter
        postHref={(post) => `/blog/${post.slug}`}
      />
    </main>
  );
}

Blog categories — app/blog/categories/page.tsx

import { RootTaleBlogCategories } from "@roottale/cms-renderer-next/server";

export default function CategoriesPage() {
  return <RootTaleBlogCategories apiKey={process.env.ROOTTALE_API_KEY!} />;
}

Blog category — app/blog/categories/[category]/page.tsx

import { RootTaleBlogList } from "@roottale/cms-renderer-next/server";

export default async function CategoryPage({
  params,
}: {
  params: Promise<{ category: string }>;
}) {
  const { category } = await params;
  return (
    <RootTaleBlogList
      apiKey={process.env.ROOTTALE_API_KEY!}
      showCategoryFilter
      activeCategory={decodeURIComponent(category)}
    />
  );
}

Blog post — app/blog/[slug]/page.tsx

import { RootTaleBlogPost } from "@roottale/cms-renderer-next/server";

export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return (
    <main>
      <RootTaleBlogPost
        apiKey={process.env.ROOTTALE_API_KEY!}
        slugOrId={slug}
        showTableOfContents
        tableOfContentsTitle="목차"
      />
    </main>
  );
}

Lead form — app/contact/page.tsx

import { RootTaleLeadForm } from "@roottale/cms-renderer-next/server";

export default function ContactPage() {
  return (
    <main>
      <h1>진단 신청</h1>
      <RootTaleLeadForm
        action="https://mysite.roottale.com/api/lead-intake"
        vertical="tax"
        redirectUrl="https://kjmtax.roottale.app/contact"
        heading="무료 진단 신청"
        description="평일 기준 1-2 영업일 내에 회신드립니다."
      />
    </main>
  );
}

The form submits via HTML POST (no JS, no fetch). After insert, the admin 302s back to redirectUrl with ?ok=1 (or ?err=<reason>). The admin checks the URL against its LEAD_INTAKE_ALLOWED_ORIGINS env — if you skip redirectUrl, the admin falls back to its LEAD_INTAKE_REDIRECT_BASE.

vertical-specific behavior

  • vertical="medical" adds the 의료 PII 국외이전 동의 checkbox (ADR-0018).
  • Omit vertical to render a select dropdown.

Exports

| Export | Use | |---|---| | RootTaleBlogList | RSC — fetches /v1/cms/public/posts?type=post and renders an <ul>. | | RootTaleBlogPost | RSC — fetches one post, renders Tiptap doc + optional TOC. | | RootTaleLeadForm | RSC — HTML form, POST → admin /api/lead-intake. | | RootTalePostCard | Single card primitive (used internally by RootTaleBlogList). | | RootTaleTableOfContents | Standalone TOC (heading list). | | RootTaleFloatingCta | Floating CTA buttons. | | renderBlocks / renderBlock | Block JSON → React element. | | attachHeadingIds / extractToc | Tiptap doc helpers. |

Security model

  • @roottale/cms-client throws if you import it into a browser bundle (assertServer()). Never expose rtlk_cust_* to the client.
  • Block JSON is server-rendered with hardcoded mark/node mappings — no dangerouslySetInnerHTML from authored content. Links pass an isSafeHref() allowlist (http/https/mailto/tel/root-relative/fragment/query).
  • The default target="_blank" on link marks gets rel="noopener noreferrer".

Compatibility

| Dependency | Range | |---|---| | react | ^19 (peer) | | next | ^14 / ^15 (any RSC-capable framework actually) | | node | >=18.18 |

License

Proprietary (UNLICENSED). Issued under the RootTale customer contract. Contact [email protected] for usage outside of an active subscription.