Skip to main content

Ratings & Comments

User engagement on project pages — when ratings make sense, why comments are tricky, and how to keep both spam-free.

Why this matters

Ratings and comments look like obvious "more engagement = better" features, but in practice they're costly. Ratings affect listing order — if you sort by rating, low-volume listings game the system with three friends voting 5 stars. Comments invite spam, harassment, and moderation overhead that crushes solo operators.

The pragmatic answer: enable ratings if and only if you have a critical mass of voters (1,000+ MAU); enable comments if and only if you have moderation bandwidth (or a community willing to self-police). For most early-stage directories, leave both off. This page documents the toggles, the data shape, and the moderation patterns that work at small scale.

Two independent features, controlled by feature flags. Both show on /project/[slug] pages when enabled.

Project detail page

Ratings

  • 1–5 stars, one rating per user per project
  • Average shown on the project card in the catalog
  • Stored in the ratings table
  • Component: StarRating in components/directory/

To sort projects by rating in the listing, pick the top_rated option — already configured in directory.config.ts.

Turning on / off

config/features.config.ts
ratings: true,   // or false to hide

Disabled → widget doesn't render, API returns 404, catalog sort option is hidden.

Comments

  • Flat list (not threaded by default)
  • Anyone logged in can post
  • Admins see a delete button on any comment
  • Stored in the comments table
  • Component: CommentSection

Turning on / off

config/features.config.ts
comments: true,   // or false to hide

Moderation

Comments aren't auto-moderated. For small directories you can watch them manually from /admin → click a project → see comments inline. For larger directories, integrate a third-party moderation API in the POST /api/comments route.

Anti-spam basics

  • Rate-limited per IP via the comments rate-limit tier (lib/rate-limit.ts)
  • Input is sanitized by sanitizeString() in lib/validations/schemas.ts (strips <>, javascript:, on*=, data: URIs)
  • Logged-in users only — no anonymous comments

A real-world walkthrough: when to enable ratings

A vintage-camera directory with 300 listings and 800 monthly active users wonders whether to enable ratings. Run the math: ~3 ratings per listing on average means a 5-star item ranks above a 4.5-star item even if the 5-star has only one rating from the operator's friend. That's noise, not signal.

The fix isn't to disable ratings — it's to require a minimum vote count before sorting honors them. In directoryConfig.sortOptions, change top_rated to filter rating_count >= 5 (defaulting to "newest" for items below the threshold). Now low-volume listings sort by recency until they earn enough ratings to be trustworthy.

For comments, the same operator delays the feature until they have a dedicated moderation volunteer. Without one, the first time a controversial listing attracts a flame war, the entire weekend is consumed deleting comments and banning users. The feature flag stays false for the first six months on purpose.

Common pitfalls

  • Enabling ratings on a brand-new directory. With 0–50 ratings across the entire catalog, the average rating signal is noise. Display ratings only after a listing crosses a minimum threshold (5+ ratings) — otherwise the "5.0 ★" badge on a one-vote listing misleads users.
  • Letting comments default to threaded discussions. Threaded comments at directory scale invariably create flame trees nobody moderates. The default flat list is the right shape — keep it.
  • Skipping comment notifications to admins. Without a notification (Slack webhook or email), problematic comments sit live for days. Wire a webhook event in webhookEvents.commentPosted(comment) if you keep comments on; review daily until you trust the community.
  • Storing comments unsanitized. The sanitizeString helper strips obvious XSS vectors, but doesn't normalise links or emoji-bombs. If you allow link-rich comments, run a second-pass URL allow-list (e.g., reject everything that's not http(s) and not on a known allow-list of domains).
  • Allowing logged-in spam farms. "Logged-in users only" doesn't stop bot accounts. Use the same Captcha challenge from your auth flow on the first comment per user to break low-effort sign-up-and-spam loops.

FAQ

Can I import historical ratings from another platform?

Yes — the ratings table is a straightforward (user_id, project_id, rating, created_at) shape. Map your CSV to those columns and bulk-insert. Beware: ratings without real users behind them feel obvious to Google's quality signals; only import if the historical users will actually log in.

Can I disable comments per project (let some discussion-friendly listings have them, others not)?

Yes — add a comments_enabled boolean column to the apps table and check it in the CommentSection component. The feature flag becomes a global default; per-project overrides take precedence. Useful for sponsored listings where you want a clean presentation page.

How do I migrate from comments-on to comments-off without losing data?

Flip the feature flag to false — comments stop rendering and the API returns 404, but the comments table is untouched. Re-enabling later restores everything. Only delete data if you intend to permanently remove the feature.