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

@risali/react

v0.7.0

Published

React server components for Risali.app — SSR-fetch editable text/image/rich-text blocks from a client's Risali site.

Downloads

822

Readme

@risali/react

React Server Components for Risali.app — fetch editable text/image/rich-text blocks from a client's Risali site at SSR time.

Built for Next.js 15 App Router (React 19). Zero client-side JavaScript: every block is rendered server-side, the editable markers ship in plain HTML.

Install

npm install @risali/react

Configure

Set the site slug once, in your project's .env:

RISALI_SITE_SLUG=mojweb
# Optional — defaults to https://app.risali.app
RISALI_API_BASE=https://app.risali.app
# Optional — Next.js fetch revalidate seconds (default 60)
RISALI_REVALIDATE_SECONDS=60

NEXT_PUBLIC_RISALI_SITE_SLUG is also accepted if you'd rather expose it.

Usage

Text

// app/page.tsx
import { RisaliText } from "@risali/react";

export default function HomePage() {
  return (
    <section>
      <RisaliText
        as="h1"
        pageKey="hero_title"
        defaultValue="Vitajte na našom webe"
        path="/"
        className="text-5xl font-bold"
      />
      <RisaliText
        pageKey="hero_subtitle"
        defaultValue="Robíme weby, ktoré klient sám upravuje."
        path="/"
      />
    </section>
  );
}

The component fetches once per (slug, path) pair — Next.js's built-in fetch cache deduplicates identical URLs in a request, so multiple <RisaliText> calls on the same page share one network call.

Image

import { RisaliImage } from "@risali/react";

<RisaliImage
  pageKey="hero_photo"
  defaultSrc="/hero-fallback.jpg"
  defaultAlt="Naša prevádzka"
  path="/"
  width={1200}
  height={600}
  className="rounded-2xl"
/>;

Only http(s)://… and /local-path sources from the Risali block are honoured; anything else falls back to defaultSrc.

Rich text (bold / italic / palette colour)

import { RisaliRichText } from "@risali/react";

<RisaliRichText
  as="div"
  pageKey="about_body"
  defaultValue="<p>Sme tu už <strong>10 rokov</strong>.</p>"
  path="/o-nas"
/>;

Block HTML is sanitised on the server before render. Whitelisted tags: span, strong, b, em, i, u, br, a, p. Whitelisted attributes: style (only color:#RRGGBB + font-size:<n>px|rem|em) and href (only http(s)://, mailto:, tel:, or /local). Everything else — <script>, <img> (use <RisaliImage> instead), onerror, javascript:, style="background: url(…)" — is stripped.

Sharing one fetch across many components

If a page renders many Risali blocks, you can pull content once and thread it down — the components skip their own fetch when content is provided:

import { getRisaliContent, RisaliText, RisaliImage } from "@risali/react";

export default async function HomePage() {
  const content = await getRisaliContent("/");
  return (
    <>
      <RisaliText content={content} pageKey="hero_title" defaultValue="Vitajte" />
      <RisaliImage content={content} pageKey="hero_photo" defaultSrc="/h.jpg" />
    </>
  );
}

Pure helpers for client components ("use client")

The <Risali*> components are async server components — they can't be used directly inside a "use client" boundary. For those cases, fetch content once in your server page and use the pure helpers inline:

// app/page.tsx — server component
import { getRisaliContent } from "@risali/react";
import { HomePage } from "@/components/HomePage";

export default async function Page() {
  const content = await getRisaliContent("/");
  return <HomePage content={content} />;
}

// components/HomePage.tsx — client component
"use client";
import { getBlockValue, getBlockImage, getBlockRichText } from "@risali/react";
import type { RisaliContent } from "@risali/react";

export function HomePage({ content }: { content: RisaliContent | null }) {
  const heroTitle = getBlockValue(content, "hero_title", "Vitajte");
  const heroPhoto = getBlockImage(content, "hero_photo", { src: "/h.jpg", alt: "Hero" });
  const aboutHtml = getBlockRichText(content, "about_body", "<p>O nás</p>");

  return (
    <>
      <h1 data-risali-key="hero_title">{heroTitle}</h1>
      <img data-risali-key="hero_photo" src={heroPhoto.src} alt={heroPhoto.alt} />
      <div data-risali-key="about_body" dangerouslySetInnerHTML={{ __html: aboutHtml }} />
    </>
  );
}

Helpers always validate the block type (a block typed image will return defaultValue from getBlockValue) and getBlockRichText always sanitises — including the fallback HTML — so a stored XSS payload can't reach the browser even if the block is missing.

Builder pages (v0.7.0)

A page with render_mode='builder' in the Risali dashboard is a DB-driven list of sections. Built-in section types: hero, text_image, features, gallery, cta, contact, plain, plus (v0.7.0) testimonials, pricing, faq, stats, team and logo_cloud. Every section is styled inline from the brand palette (gradients / shadows / radius derived via color-mix() from --risali-color-*) and is responsive without media queries, so it looks designed even without the host app's Tailwind theme. <RisaliPage> renders the whole page server-side; pages still in code mode return notFound, so a catch-all route coexists with your static routes:

// app/[[...rest]]/page.tsx
import { notFound } from "next/navigation";
import { RisaliPage } from "@risali/react";

export default async function Page({ params }: { params: Promise<{ rest?: string[] }> }) {
  const { rest } = await params;
  const path = "/" + (rest ?? []).join("/");
  const page = await RisaliPage({ path });
  if (!page) notFound();
  return page;
}

Signature sections you build in code register via customSections={{ moja_sekcia: MyComponent }} and win over the built-in catalog on a type collision.

<RisaliBrand /> in the root layout emits the brand palette + fonts as CSS variables (--risali-color-<key>, --risali-font-heading, --risali-font-body) — builder sections consume them automatically, and your own components/Tailwind theme can too. A brand change in the dashboard revalidates the site instantly, no deploy.

Editor mode

Every rendered block carries a data-risali-key="<pageKey>" attribute. The Risali editor iframe (/risali.js?site=<slug>&risali_edit=1) uses that marker to wire up click-to-edit — no CSS selector generation, no content-hash drift, no fragile DOM scanning.

What this package does NOT do

  • No client-side DOM patching. Content lives in the server-rendered HTML; the visitor browser never re-renders Risali content.
  • No analytics or form capture. Those still ship via <script async src="https://app.risali.app/risali.js?site=…"></script> (the Risali snippet handles pageview events, form beacons, the cookie banner, and the editor iframe).
  • No widgets (booking, pricelist, contact form) — yet. Coming in a follow-up package release.

License

Closed-source. Used by Risali.app clients under their SaaS subscription.