@graficos/nuxt-comments
v0.5.1
Published
Embeddable, unstyled comments system for Nuxt 4, backed by Cloudflare D1 and authenticated via @nuxtjs/better-auth.
Downloads
1,227
Maintainers
Readme
@graficos/nuxt-comments
An embeddable, unstyled comments system for Nuxt 4, backed by Cloudflare D1 and authenticated through the host application's Better Auth setup.
@graficos/nuxt-comments is a reusable Nuxt module — not a hosted widget. It ships the threaded comments primitive (data model, API, composables, unstyled components) and leaves identity to @nuxtjs/better-auth and presentation to you.
Features
- 🧶 Threaded comments (adjacency list, arbitrary nesting via
parentId) - 📖Cursor-based pagination for top-level comments and replies
- 😊 Reactions with configurable types and database-enforced uniqueness
- 🔑 Server-side ownership checks (edit/delete your own comments only)
- 🕳️ Soft deletion that preserves threads (
[deleted]placeholders) - 🚫 Admin user deletion (
deleteCommentsUser) with documented semantics - 🧹 PII erasure primitive (
DELETE /api/_comments/users/:userId) that leaves no user id, name, avatar, body or reaction behind — see privacy.md - 💪🏽 Shared validation, consistent HTTP error envelope (thanks to Valibot)
- ⏱️ Rate-limiter extension point (Cloudflare-native integration documented)
- 🎨 Unstyled, accessible components with typed slots and per-layer class props
- 🌍 i18n-ready: override every string (including
aria-labels) viacomments.messages— no i18n dependency - 🔄 Adapter boundary for alternative persistence backends (potentially future feature)
Why
- Nuxt-native. Server routes, composables and components are registered by the module. No external comment service, no embed script, no analytics.
- Auth is borrowed, not rebuilt. The package consumes the Better Auth session. Any provider Better Auth supports works without provider-specific code.
- Unstyled by design. Semantic HTML, accessibility attributes, props/slots/events. You own the CSS, the layout, the auth buttons, the emoji.
- Backend-abstracted. The domain talks to a
CommentsStoreinterface; D1 is the only shipped implementation. A future GitHub Discussions mirror can be added behind the same boundary. - One database per site. No tenant column, no cross-site data. Each Nuxt installation gets its own D1 binding.
- Resource-scoped. Comments belong to an opaque resource identifier (usually a blog post path).
Architecture
Better Auth ──► session (who is the current user?)
│
Vue/Nuxt UI ──► Comments API (/api/_comments/...) ──► CommentsService
(unstyled) │
CommentsStore (interface)
│
D1CommentsStore ──► D1- Server route handlers are thin: parse → session → service → store.
CommentsServiceowns validation, authorization, ownership and deletion policy.D1CommentsStoreowns SQL. The domain never imports D1 types.- See docs/adapters.md for the future GitHub Discussions architecture (not implemented).
Requirements
- Nuxt
>= 4.0.0 @nuxtjs/better-auth(installed automatically as a peer + module dependency in npm/pnpm; see docs/authentication.md)- A Cloudflare D1 database and a Wrangler binding for comments (see docs/cloudflare-d1.md)
- A Better Auth database for durable identities — or accept the no-persistence fallback (OAuth-only, no email/password, no server-side session revocation). Cloudflare D1 can host both the auth tables and the comments.
wrangleravailable to run migrations
Install
pnpm add @graficos/nuxt-comments@nuxtjs/better-auth is declared as both a peer dependency and a Nuxt moduleDependencies entry. npm/pnpm/yarn auto-install peer dependencies; if your package manager does not, install it explicitly:
pnpm add @nuxtjs/better-authMinimal setup
nuxt.config.ts:
export default defineNuxtConfig({
modules: [
// @nuxtjs/better-auth is auto-installed via moduleDependencies —
// you do not need to list it here (listing it is harmless).
"nuxt-comments",
],
comments: {
database: {
binding: "DB", // Same as the Wrangler binding name for your D1 database for the comments
},
// Opt-in: persist Better Auth users/sessions in D1 (email/password, OAuth,
// revocation). Omit to leave Better Auth on its in-memory default.
auth: {
database: {
binding: "DB", // Binding for Better Auth's tables; may equal the comments binding
},
},
},
});wrangler.jsonc (or wrangler.toml) — the binding name must match comments.database.binding:
{
"d1_databases": [
{
"binding": "DB",
"database_name": "my-comments",
"database_id": "<your-database-id>",
},
],
}Better Auth configuration files (server/auth.config.ts and app/auth.config.ts) are created by the Better Auth module. Add providers there:
// server/auth.config.ts
import { defineServerAuth } from "@nuxtjs/better-auth/config";
export default defineServerAuth({
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
});.env:
NUXT_BETTER_AUTH_SECRET=<at least 32 random chars>
NUXT_PUBLIC_SITE_URL=http://localhost:3000Auth persistence. Better Auth stores users, sessions and accounts. Without a database,
@nuxtjs/better-authfalls back to Better Auth's in-memory adapter (email/password unavailable, sessions do not survive the current isolate). Setcomments.auth.database.bindingto persist them in D1. See docs/authentication.md#turnkey-d1-persistence-opt-in.
Run the package migrations against your local D1. Either copy the shipped SQL into your own migrations/ folder, or point a Wrangler config's migrations_dir at the package:
# recommended: copy the package migrations into your app's migrations/ dir
cp -r node_modules/@graficos/nuxt-comments/migrations/* ./migrations/
# apply locally, then remotely when deploying
npx wrangler d1 migrations apply my-comments --local
npx wrangler d1 migrations apply my-comments --remoteAuth tables.
migrations/auth/holds Better Auth's tables and is only needed when you setcomments.auth.database.binding. Wrangler'smigrations_diris not recursive, so apply it with a separate Wrangler config pointing at that folder. See docs/authentication.md.
Upgrading.
migrations/0002_user_id_nullable.sqlmakescomments.user_idnullable so user erasure can leave no trace. Copy the new file (or re-pointmigrations_dir) and re-runwrangler d1 migrations apply; already-applied migrations are skipped.
Then use the component:
<template>
<Comments resource="/blog/my-post" />
</template>Component prefix. Nuxt best practice is to prefix module exports, so the default
components.prefixis'Nuxt'and the components are<NuxtComments>,<NuxtComment>, etc. Setcomponents.prefix: ''to get the unprefixed<Comments>shown above. See docs/configuration.md.
Documentation
| Document | Contents |
| ------------------------------------------ | --------------------------------------------------------------------------------------------- |
| Configuration | Module options, runtime config, env vars, pagination, reactions, limits, resource identifiers |
| Authentication | Better Auth integration, providers, email-optional identity, consumer-owned login UI |
| Cloudflare & D1 | Database creation, Wrangler binding, local development, migrations, production |
| Components | <NuxtComments> props/slots/events, composables, types, styling strategy |
| Server API | Endpoints, validation, errors, pagination, deletion policy |
| Privacy & erasure | Stored personal data, the PII erasure primitive, account-deletion recipe, controller duties |
| Adapters | CommentsStore boundary, D1 adapter, future GitHub Discussions architecture |
| Playground | Running the demo app locally, OAuth setup |
Development
pnpm install
pnpm run dev:prepare # stub-build the module + prepare the playground
pnpm run playground:migrate
pnpm run dev # http://localhost:3000Quality gates:
pnpm run lint
pnpm run test # workers-runtime + node/nuxt test suites (101 tests)
pnpm run test:types
pnpm run prepack # production build of the published package
# Browser end-to-end tests (Playwright) against the playground
pnpm --dir playground exec playwright install chromium # one-time
pnpm --dir playground e2eSee playground/README.md for OAuth setup, the admin deletion demo, and the end-to-end tests.
Package structure
src/
module.ts # defineNuxtModule: meta, moduleDependencies, registration
types.ts # module option types + runtime config augmentation
runtime/
app/components/ # unstyled Vue components
app/composables/ # useComments, useCommentsSession
shared/ # types, resources, Valibot schemas (server + client)
server/
api/_comments/ # Nitro route handlers (thin)
services/ # CommentsService, admin deletion, session helpers
repositories/ # CommentsStore interface + D1CommentsStore
utils/ # store resolution, errors, HTTP mapping, rate limiting
migrations/
0001_init.sql # comments tables
0002_user_id_nullable.sql # nullable user_id for PII erasure
auth/ # Better Auth tables (opt-in D1 provider)
playground/ # Nuxt 4 app exercising the module
e2e/ # Playwright end-to-end tests + fixtures
test/
unit/ # domain + service + resource tests
api/ # API handlers on real Miniflare D1
nuxt/ # component tests in a Nuxt environment
module/ # module registration/config testsLicense
MIT
