Blog/React SDK

React Comments SDK 1.0.0: What's New

By Joris Obert5 min read

@goodvibeslab/gvl-comments-react is now 1.0.0 on npm. It's a drop-in comment section for React 18+ with threaded replies, reactions and AI moderation. Comments are stored and served for you, so there's no backend to build.

This release is less about new screens and more about trust: security hardening, a long list of fixes, fewer network requests, and parity with the Flutter SDK. Coming from 0.9.x? Upgrading is a version bump: every change is additive.

A Comment Section in Three Steps

If you haven't tried it yet: create a free project on the dashboard, add your site's domain, and copy the install key (cmt_live_…). Install keys only work on the domains registered on the project, so they're safe to ship in the browser.

npm install @goodvibeslab/gvl-comments-react
import { CommentsProvider, CommentsThread } from "@goodvibeslab/gvl-comments-react";

<CommentsProvider
  config={{
    installKey: "cmt_live_xxx",
    externalUserId: currentUser.id,   // your own auth, any stable id
    externalUserName: currentUser.name,
  }}
>
  <CommentsThread threadKey="post:550e8400-e29b-41d4-a716-446655440000" />
</CommentsProvider>

CSS is injected automatically, dark mode follows prefers-color-scheme, and everything is themable through CSS variables. The full walkthrough is in the React SDK docs.

Security Hardening

A comment widget renders text written by strangers, on your pages. 1.0.0 tightens every place where that text, or the user behind it, touches the browser:

  • Tokens are bound to one user. When the signed-in user (or externalUserId) changes, the previous user's token is never reused, so a post, reaction or report can't be attributed to the wrong person.
  • The link parser can't freeze the page. URL detection used to run in quadratic time on crafted input. It's now bounded: a 5,000-character comment is parsed in under a millisecond. It also drops regex lookbehind, which Safari before 16.4 can't parse.
  • Links ask before opening, and only http, https and mailto links are ever produced.
  • Avatar URLs are validated: http(s) only, loaded lazily with no-referrer, and replaced by initials when an image fails.
  • Errors no longer leak server payloads, and every request times out after 15 seconds.

CommentCount, TopComment and Read-Only Mode, Fixed

Three features were documented but didn't behave as promised. They do now.

CommentCount

It called an API route that doesn't exist, so it never showed a number. It now uses the batched thread-info endpoint: counters rendered together, like a feed of cards, are merged into a single request.

<CommentCount threadKey={post.threadKey}>
  {(count, loading) => (loading ? "…" : `${count} comments`)}
</CommentCount>

TopComment

It showed the first comment of the thread instead of the most-engaged one. It now asks the server for the top comment, and shows the moderation placeholder when that comment was moderated or reported.

<TopComment threadKey={post.threadKey} onTap={() => navigate(post.url)} />

Read-only mode

Leave out externalUserId and visitors can read the thread without signing in. In 0.9.x that showed an empty thread; now comments load, writes stay disabled, and onSignInTap lets you turn the composer into a “Sign in to comment” call to action.

<CommentsProvider config={{ installKey: "cmt_live_xxx" }}>
  <CommentsThread threadKey={threadKey} onSignInTap={() => router.push("/login")} />
</CommentsProvider>

Errors Your Users Can See, and Recover From

Only the first load used to report failures. A failed send, “load more” or refresh failed silently. CommentsThread now shows these errors inline, with a Retry button when retrying can help, and a failed send gives users back their text and the comment they were replying to.

When a project uses up its monthly quota, the thread says “Comments are paused for now” instead of showing a Retry that can't succeed. Reading keeps working. Building your own UI? The hook exposes the same information:

const { comments, send, notice, dismissNotice } = useCommentsThread({
  threadKey,
});

if (notice?.code === "quota_exceeded") {
  // only new comments are paused; retrying won't help
}
// other codes: "rate_limited", "timeout"

Client calls throw CommentsApiException with the same code, and MAX_COMMENT_LENGTH (5,000) is exported for your own validation.

Fewer Surprises in the Thread

  • A refresh during a send could show the new comment twice, or drop it.
  • A slow response for a previous thread or user could overwrite the current one.
  • Nested replies could disappear depending on the order they loaded in. They are now sorted oldest first under their root comment.
  • A failed reaction stayed on screen although the server never recorded it. It's now rolled back with a message, and rapid double taps no longer miscount.
  • An invalid date could crash the whole list.
  • Enter sent the comment in the middle of an IME composition (Chinese, Japanese, Korean input).

All fixed in 1.0.0.

Fewer Requests

  • One token per user for every thread and operation. The SDK used to re-authenticate whenever the thread changed.
  • Profile sync runs alongside the first load instead of before it, and is skipped when the profile hasn't changed. Moderation settings are fetched once.
  • Batched thread info: prefetching loads up to 50 threads per request, where it used to make two requests per thread.

Small Things You'll Notice

  • A Like button under each comment, on top of the six reactions.
  • “Sending…” in place of the timestamp while a comment is on its way.
  • A confirmation step before reporting, and “Already reported” feedback.
  • A 5,000-character limit on the composer, with a counter near the limit.
  • Relative timestamps (“3 min ago”) that refresh every minute.
  • A reaction picker you can use from the keyboard, with focus moved in and restored, plus labelled reaction counts and composer.

Using It with the Next.js App Router

The components use React context and browser APIs, so they render on the client. In the App Router, put them in a file marked "use client" and use that component from your server pages:

// app/posts/[id]/Comments.tsx
"use client";

import { CommentsProvider, CommentsThread } from "@goodvibeslab/gvl-comments-react";

export function Comments({ threadKey, user }: { threadKey: string; user?: { id: string; name: string } }) {
  return (
    <CommentsProvider
      config={{
        installKey: process.env.NEXT_PUBLIC_GVL_INSTALL_KEY!,
        externalUserId: user?.id,       // omit for read-only visitors
        externalUserName: user?.name,
      }}
    >
      <CommentsThread threadKey={threadKey} />
    </CommentsProvider>
  );
}

That's how the comments under this article are wired.

How to Upgrade

npm install @goodvibeslab/gvl-comments-react@^1.0.0

No code changes are needed: 1.0.0 only adds props, components and exports. externalUserId is now optional (that's what enables read-only mode), and the unused @supabase/supabase-js dependency is gone, so your install gets lighter. The full list is in the changelog.

Try the React SDK 1.0.0

Add a moderated comment section to your React app in minutes. Free up to 1,000 comments a month, AI moderation included, no credit card.