Project · active

Feedback Fusion

A community-driven feedback platform with voting, public roadmap, and admin dashboard — upgraded with streaming Suspense, Server Actions, and double-lock auth.

Tue, February 10, 2026
5 min read
View on GitHub
Tech Stack:
Next.js 16 TypeScript Tailwind CSS v4 Prisma ORM PostgreSQL Clerk Auth React Hook Form Zod shadcn/ui
LOC 2,478
Languages TypeScript, TSX, CSS
Files 57

Overview

Feedback Fusion is a full-stack feedback management platform where users submit ideas, vote on features, and track progress through a public Kanban-style roadmap. It was built as an upgraded version of a reference implementation, with a focus on performance, type safety, and security hardening.

The project demonstrates how modern Next.js patterns — React Suspense streaming, Server Actions, and dual-layer authentication — can transform a standard CRUD app into a production-ready product tool.

Live Demo: nextjs-feedback-fusion.vercel.app Source Code: github.com/brian-dizon/nextjs-feedback-fusion


Problem It Solves

Product teams need a transparent way to collect and prioritize user feedback. Spreadsheets and email threads lose ideas; paid tools charge per seat for basic voting. Feedback Fusion provides a self-hosted alternative where the community drives the roadmap through democratic voting, and admins maintain control through a secure dashboard.


Architecture

The application uses Next.js 16 App Router with a clear separation between public pages, authenticated user flows, and protected admin routes.

Route Structure

  • feedback/ — Community feedback board with category filtering and voting
  • feedback/new/ — Submission form with client and server-side validation
  • roadmap/ — Public Kanban board showing feature status and progress statistics
  • admin/ — Secured admin dashboard for managing feedback status
  • sign-in/, sign-up/ — Custom themed Clerk authentication pages
  • api/votes/ — REST endpoint for toggle voting (requires auth)
  • api/feedback/[id]/status/ — Admin-only endpoint for status updates

Data Flow

  1. Authentication — Clerk handles OAuth and email/password login; a syncCurrentUser helper maps Clerk identities to the local database, creating users on first sign-in and auto-promoting the first user to admin
  2. Streaming — Static headers and layouts render instantly; dynamic data streams in via React Suspense with skeleton fallbacks
  3. Mutations — Server Actions handle feedback submission with Zod validation; API routes handle votes and admin status updates
  4. Security — Double-lock: UI hides admin links for non-admins, and API routes independently verify the database role

Database Schema

PostgreSQL with Prisma ORM. Three core models:

  • users — Mapped from Clerk identities with role (user/admin)
  • posts — Feedback items with title, description, category, and status (under_review/planned/in_progress/completed)
  • votes — Many-to-many join between users and posts with unique constraint preventing duplicate votes

Key Technical Decisions

Clerk over Better Auth

Clerk was chosen for its hosted authentication infrastructure, built-in OAuth providers, and seamless Next.js integration. The tradeoff is vendor dependency, but for a weekend build focused on feedback features rather than auth infrastructure, Clerk accelerated development significantly.

React Suspense Streaming

The original reference implementation blocked on database queries before rendering any UI. Feedback Fusion wraps all data-dependent components in Suspense boundaries, allowing headers, navigation, and static content to appear instantly while database queries resolve in parallel.

Server Actions for Forms

Feedback submission uses Next.js Server Actions instead of API routes. This eliminates client-side fetch boilerplate, provides automatic type safety, and enables progressive enhancement — forms work without JavaScript.

Double-Lock Authorization

Admin security is enforced at two levels: the UI conditionally renders admin navigation, and API routes independently check the database role. Bypassing the UI is insufficient; the server rejects unauthorized requests regardless of client state.

In-Memory Statistics

The roadmap page calculates vote counts, completion percentages, and category breakdowns in-memory from the fetched dataset rather than issuing separate aggregation queries. This reduces database round-trips by approximately 75% on the roadmap view.


What Was Built

Community Feedback Board

Users browse feedback by category, view vote counts, and submit new ideas. The category sidebar shows item counts and color-coded icons. Each feedback card displays the author, vote count, and current status.

Voting System

Authenticated users toggle votes on feedback items. The UI updates optimistically while the API persists the change. Duplicate votes are prevented at the database level via a unique composite index on (userId, postId).

Public Roadmap

A four-column Kanban board (Under Review, Planned, In Progress, Completed) with progress statistics, overall completion percentage, and vote-sorted cards within each column. Status badges indicate active work or shipped features.

Admin Dashboard

Role-gated table showing all feedback with status management. Admins update item status via dropdown, which triggers revalidation and updates the public roadmap instantly. Unauthorized access attempts are logged and redirected.

User Synchronization

A syncCurrentUser utility bridges Clerk and the local database. On first login, it creates a database user and auto-assigns admin role if they are the first user. Subsequent logins update name, email, and avatar from Clerk.


Metrics

MetricValue
Total LOC2,478
Files57
LanguagesTypeScript, TSX
Database Models3
UI Components12 shadcn/ui primitives

Lessons Learned

Suspense boundaries must be granular. Wrapping an entire page in a single Suspense boundary defeats the purpose. Splitting static headers from dynamic content creates the perception of instant loading even when database queries take seconds.

Clerk’s currentUser() is server-only. Attempting to call it in client components silently fails. The sync pattern — server-side user creation with client-side ID reference — bridges the gap cleanly.

Database roles are the source of truth. Client-side role checks improve UX but cannot replace server verification. The double-lock pattern prevents privilege escalation even if the frontend is tampered with.

Server Actions simplify forms but complicate error handling. Unlike API routes where HTTP status codes communicate failure, Server Actions return serializable objects. A consistent { success, error } response shape keeps error handling predictable across the app.


Next Steps

  • Email notifications — Notify users when their feedback changes status or reaches a vote threshold
  • Comments — Threaded discussion on feedback items
  • Search and filter — Full-text search across titles and descriptions
  • Public API — REST endpoints for third-party integrations
  • Webhook support — Trigger external services on status changes