Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

Build a Feature Voting Board With Next.js -- Collect User Feedback Without Paying for Canny

July 22, 2026
nextjssaasdrizzle-ormboilerplatestartup

You just launched your SaaS. Users are signing up, but you are not sure what to build next. Someone tells you to "just use Canny." Then you look at the pricing: $79 per month for a feature request board.

That is a real cost before you have real revenue. And it means your user feedback lives in someone else's database, visible only through their dashboard.

There is a better path. A feature voting board is a few database tables and a handful of pages. If you are already using the Next.js SaaS boilerplate, you can build it this weekend and keep every user suggestion inside your own app.

What a Feature Voting Board Actually Needs

Strip away the pricing tiers and it comes down to four things:

  1. A place for users to submit a feature idea (title and description)
  2. A way for other users to upvote ideas they want
  3. A status field so you can mark ideas as planned, in progress, or shipped
  4. A feed that sorts by vote count so the most-wanted ideas surface first

Nothing in that list requires a third-party tool. It requires a schema, two API routes, and a page.

The Data Model

With Drizzle ORM and Neon DB, the schema looks like this:

export const featureRequestTable = pgTable('feature_requests', {
  id: uuid('id').defaultRandom().primaryKey(),
  authorId: uuid('author_id').notNull().references(() => userTable.id),
  title: text('title').notNull(),
  description: text('description'),
  status: text('status').notNull().default('open'), // open | planned | in_progress | shipped
  voteCount: integer('vote_count').notNull().default(0),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});
 
export const featureVoteTable = pgTable('feature_votes', {
  id: uuid('id').defaultRandom().primaryKey(),
  userId: uuid('user_id').notNull().references(() => userTable.id),
  requestId: uuid('request_id').notNull().references(() => featureRequestTable.id),
  createdAt: timestamp('created_at').defaultNow().notNull(),
}, (t) => [unique().on(t.userId, t.requestId)]);

The unique constraint on (userId, requestId) means one vote per user per feature -- no gaming the board.

How Voting Works

When a user clicks upvote, you insert a row into feature_votes and increment voteCount on the request. When they click again, you delete the vote and decrement. Both writes are wrapped in a Drizzle ORM transaction so the counter never drifts.

Your service layer owns this logic. The route calls featureService.toggleVote(requestId, userId) -- no business logic leaks into the handler.

Since auth is already wired via JWT, you get the userId from getUserFromRequest(req) at the top of every route. No extra setup. You already know who is voting.

The Page That Matters

The board page is a server component. It fetches all open requests sorted by voteCount descending and shows which ideas the current user has already voted for. No client-side fetch needed for the initial render -- the list is ready by the time the page lands.

A filter lets users switch between status tabs: All, Planned, In Progress, Shipped. This is the same pattern as any data table in the boilerplate: URL search params drive a server component query, and Shadcn UI renders the tabs.

Once you collect enough votes, you can pair the board with feature flags to ship the top-voted features to your most engaged users first, before rolling out to everyone.

When to Build It Yourself vs. Use Canny

Build it yourself if:

  • You want feedback inside your app, from users who are already logged in
  • You prefer not adding $79/month before month one
  • You want full control over what data you collect and how it is displayed

Use Canny if:

  • You need a public feedback board before your app is live
  • You want a hosted changelog with automatic email digests
  • The build time is the actual bottleneck for your launch right now

For most early-stage SaaS products, the self-built board wins. You already have the auth, the database, the component library, and the service layer pattern. The incremental cost is an afternoon.

What You Skip by Owning It

  • $79/month to a third party (before a single paying user)
  • A separate login and admin dashboard for your team
  • User feedback that lives outside your database
  • A third-party cookie banner for an embedded widget
  • Manual copy-paste to mark features shipped inside your product

Your users submit feedback inside the product they are already logged into. You ship the update, change the status to Shipped, and the board reflects it immediately.

Ship It This Weekend

The Next.js SaaS boilerplate already has JWT auth, Drizzle ORM, Neon DB, and Shadcn UI wired up. Add the two tables from the schema above, write a service with toggle-vote logic, and you have a working feedback board before your next standup.

Your users have opinions about what to build next. This is the cheapest way to hear them -- and the only way to keep that signal where it belongs: inside your own product.