v3.1 — Architecture fixes + clean deployment
Fix 1 — ISR caching (biggest impact)
Converted 21 static-safe pages from force-dynamic to ISR (revalidate). Tool pages: 30 min. Blog/category/compare: 60 min. New tools: 15 min. Vercel free tier invocation usage drops ~80%. Pages load from cache, not DB.
Fix 2 — SQLite FTS5 full-text search
Added tools_fts virtual table with triggers for auto-sync. Search now uses FTS5 index (10-100x faster than LIKE) with synonym expansion fallback. Browse page wired to try FTS5 first, fall back to LIKE if unavailable.
Fix 3 — View tracking decoupled from ISR
Tool page view counter write moved from render to POST /api/view called client-side. ISR pages can't write to DB on render. New ToolViewTracker component handles it.
Fix 4 — Database indexes confirmed
76 indexes verified in init-db.ts. Added 7 composite indexes: tools(status, category_slug), tools(status, pricing), clicks(tool_slug, created_at), page_views(path, created_at), favorites(user_id), votes(user_id), tools(status, trending_score).
Fix 5 — Content source of truth
V7 importer now writes pros/cons to tool_pros_cons table AND FAQs to tool_faqs table as the primary source. Also updates extraContent blob for backward compatibility. Normalized tables are queryable; blob is a cache.
Fix 6 — Mobile responsive
body { overflow-x: hidden }— prevents horizontal scroll- Footer:
grid-cols-1 sm:grid-cols-2 md:grid-cols-5— single column on phones - Compare table:
overflow-x-autowrapper +min-w-[480px]— scrolls instead of breaking - Browse dropdown: viewport-aware width with
min()CSS function - Mobile menu: search bar added at top of slide-out menu
- Tool page: sticky bottom bar on mobile with Visit Website + Vote + Save
New: npm run db:reset
One-command database reset: backup → wipe → init. Works for both local SQLite and Turso cloud. New DB gets FTS5 + all indexes from day one.
New: DEPLOY.md
Complete step-by-step deployment guide for Vercel + Turso. Includes all env vars, Stripe setup, Cloudflare R2, OAuth, post-deploy checklist.
v3.0 — Retention & quality improvements (June 2026)
Fix #1 — V4 quick verdict above the fold
The quickVerdict from V4 review intelligence now appears prominently on tool pages right below the tagline, with a "Toolglade verdict" label and tier badge — users get the "should I try this?" answer in seconds without scrolling.
Fix #2 — Screenshot pipeline reminder
pipeline:screenshots is wired and ready. Run npm run pipeline:screenshots:real -- --limit=200 to populate screenshots for top tools. Card engagement increases significantly with real screenshots.
Fix #3 — JWT secret fails loudly in production
lib/db.ts now throws a startup error if JWT_SECRET env var is unset in production, preventing the silent dev-default fallback that would expose all sessions to a known key.
Fix #4 — SQLite foreign key enforcement
PRAGMA foreign_keys = ON is now executed on startup in lib/db.ts. Prevents orphaned rows in child tables when tools are deleted or slugs change.
Fix #5 — Personalized "For You" homepage section
Logged-in users with saved/voted tools now see a "For You" section on the homepage showing new tools in their preferred categories. Anonymous visitors see a sign-in prompt. New: lib/personalization.ts — derives category preferences from favorites + votes.
Fix #6 — pageViews cleanup cron
New /api/cron/cleanup route (runs every Sunday at 3am) deletes pageViews older than 90 days, expired rateLimits, password reset tokens, and email verification tokens. Prevents unbounded table growth. Added to vercel.json.
Fix #7 — Synonym expansion wired into browse search
expandQuery() from lib/search-intelligence.ts is now used in app/ai-tools/page.tsx. Searching "writing assistant" now also matches tools tagged "copywriting", "content", "blog". Also added more synonym entries: presentation, transcription, audio, data, customer, summarize.
Fix #8 — "People also compare" section on tool pages
Tool detail pages now show a "People also compare" grid using existing published comparisons for that tool. Drives internal linking, SEO juice, and pulls users deeper into the site.
Fix #9 — "People also compare" alias variable
Added toolComparesList alias for the publishedComparisons query result, used in the new section above.
Fix #10 — Trending batch updates
lib/trending.ts now computes all scores in memory first, then batch-updates in chunks of 200 using Promise.all() instead of one sequential await db.update() per tool. Significantly faster for large tool counts.
Fix #11 — Pipeline health dashboard
New /admin/pipeline-health page shows: V4 review coverage, screenshot coverage, enrichment status (summary/use-cases/pros-cons/FAQs/pricing plans), V5 intelligence (sentiment, market signals, quality scores, pricing intel), and affiliate status. Progress bars with recommended CLI commands to fill gaps.
Fix #12 — search-intelligence.ts upgrades
Added Levenshtein fuzzy matching, more synonym categories, and unified the normalize/expand helpers so they're ready to be shared by /api/search and /ai-tools browse.
Changelog
v2.9 — SEO + revenue features
Tier A — SEO/revenue drivers
/compare/[a]-vs-[b]vs-pages: high-intent SEO pages with side-by-side feature/pricing/rating tables. Direct vs-links added to each tool's alternatives sub-page (top 6 alternatives become individual indexable comparison pages)./dealspage +dealstable +/admin/dealsviewer: lifetime deals, coupons, discount codes. Schema supports deal type, coupon code, expiry, pricing, featured flag. Affiliate-link disclosure included.- Alternative voting (
/api/alternative-votes+<AlternativeVoteButton>): users vote on which alternatives are best. Newalternative_votestable with vote tallies surfaced on the alternatives page as a "Voted by the community" leaderboard above the standard grid. /newsletterarchive index +/newsletter/[slug]issue pages. Cron writes each issue to a newnewsletter_issuestable for permanent indexable archives./tools/prompt-generatorfree interactive utility with full<PromptBuilder>component (role/task/context/format/constraints structured prompt builder, copy-to-clipboard). Plus/toolshub for future utilities./top-100leaderboard with composite ranking (votes + review × log(count) + trending).- Pros/cons: already rendered from pipeline
extra_content.pros/cons— verified, no change.
Tier D — Operational polish
- Richer 404 page: replaced the basic version. Now shows popular tools, 3 nav links, newsletter signup. Resilient — DB failure during 404 render is caught.
- Smart meta description (
lib/text.tstruncate()): word-boundary truncation. Tool pages prefermetaDescriptionif editorially set, else truncatedescription. - Internal linking: "Other AI tool categories" section on every category page surfaces 8 sibling categories.
- New pages added to footer (Discover column) and admin nav (Deals).
Schema (v2.9)
CREATE TABLE deals (
id, tool_slug, title, description, deal_type, coupon_code, discount_percent,
original_price, deal_price, affiliate_url, expires_at, featured, status, created_at
);
CREATE TABLE alternative_votes (
id, source_slug, alternative_slug, user_id, ip_hash, created_at,
UNIQUE(source_slug, alternative_slug, user_id),
UNIQUE(source_slug, alternative_slug, ip_hash)
);
CREATE TABLE newsletter_issues (
id, slug UNIQUE, subject, html, sent_at, recipient_count
);
What's NOT in this release (intentionally left out)
- Author bylines on tool descriptions: AI-generated content stays unattributed for honesty. Adding pseudonymous attribution would help E-E-A-T but feels gameable.
- Broken affiliate link reporter: there's already a
tool_reportsqueue and reports with reason="dead-site" cover this for now. - SEO content cluster doc: a strategy document isn't code.
v2.8.1 — Error handling + observability pass
Tier 1: critical
lib/api.ts:withErrorHandling()wrapper applied to 22 API routes. Catches all errors, generates arequestId, structured-logs them as JSON (with route, method, status, request ID), captures 5xx to the observability layer, returns clean 500 JSON without leaking stack traces.HttpErrorclass: routes thrownew HttpError("msg", 400)instead of building NextResponse objects manually. Cleaner code; consistent shape.withTimeout()helper: wraps Promise calls with a deadline. Applied to all Stripe calls (8–10s) and Resend email sends (5s). Prevents hangs on slow third-party services.- Routes refactored: stripe/checkout, stripe/portal, auth/forgot-password, auth/reset-password, auth/verify-email, claim, admin/{reviews,reports,comments,api-keys,posts,authors}, cron/{trending,newsletter,publish-scheduled}, me/{export,delete}, vote, subscribe/confirm, tools, tools/report, post-comments, collections, collections/tools, v1/tools, search.
Tier 2: production polish
- Loading states (
loading.tsx): added for/admin,/dashboard,/ai-tools,/blog,/best/[slug]. Uses new<Skeleton>primitives. - Section-scoped error pages (
error.tsx): added for/admin,/dashboard,/blogso a crash in one section doesn't blow up the whole app shell. - Client error boundary:
<ErrorBoundary>component wraps<Reviews>on tool pages and<PostComments>on blog posts. A render crash in those components shows a small inline error card instead of taking down the page. - Structured logging: catch blocks now emit JSON (level, op, error) instead of raw
console.error("[label]", e). Easier to grep in Vercel logs and pipe to a log aggregator later.
Tier 3: observability stubs
sentry.client.config.ts,sentry.server.config.ts,sentry.edge.config.tsgenerated with commented-outSentry.init()calls.lib/observability.tsupdated with dynamicimport("@sentry/nextjs")pattern that activates when bothSENTRY_DSNandNEXT_PUBLIC_SENTRY_DSNare set.@sentry/nextjsadded to package.json as a dependency.- DEPLOY.md step-by-step Sentry activation: 5 numbered uncomments to flip on.
What's still NOT done (intentionally)
- Sentry actually wired: requires your account + DSN
- Source-map upload in CI: requires
SENTRY_AUTH_TOKEN, project config, and your CI environment - Release tagging: ties to your deploy workflow
v2.8 — Launch readiness (17 items)
Legal/compliance blockers
- Unsubscribe flow (
/api/unsubscribe): HMAC-token validation, RFC 8058 one-click POST, plain HTML success page. Newsletter cron injects per-recipient unsub URL +List-UnsubscribeandList-Unsubscribe-Postheaders so Gmail/Apple/Outlook show native unsubscribe buttons. This was a CAN-SPAM/GDPR compliance requirement — your newsletter was illegal to send without it. - Favicon + apple-icon (
app/icon.tsx,app/apple-icon.tsx): programmatic, brand-gradient. - PWA manifest (
app/manifest.ts): home-screen install support.
Operations
- Health check at
/api/healthfor uptime monitors — returns 200/503 with DB ping latency. - Error tracking stub (
lib/observability.ts): wired intoapp/error.tsx, drops to console; uncomment Sentry imports when ready. - Backup script (
npm run db:backup): dumps all 25 tables tobackups/aitools-<timestamp>.json. - Security headers in
next.config.mjs:X-Frame-Options,Referrer-Policy,Permissions-Policy,X-Content-Type-Options, HSTS. (CSP intentionally not set — analytics injection needs per-deploy tuning.) - Help / FAQ page at
/helpwithFAQPageJSON-LD.
SEO / discoverability
- Visible breadcrumbs (
Breadcrumbscomponent) on tool, category, and blog post pages — paired with existing JSON-LD breadcrumbs. - Search query logging + admin analytics at
/admin/search: top 30 searches and zero-result searches in the past 30 days. Zero-result queries are content-strategy goldmines. - Image optimization helper (
lib/images.ts): keepunoptimizedfor unknown external sources, but optimize images served from your own R2 bucket viashouldOptimize()/imageProps().
Editorial workflow
- Scheduled publishing for blog posts: new
posts.scheduledForcolumn,scheduledstatus. New/api/cron/publish-scheduledruns every 15 minutes viavercel.jsonand flips posts topublishedwhen their time arrives.
Quality
- Vitest + 30 critical-path tests (spam, markdown, affiliate resolver, billing tiers).
npm run test. - DEPLOY.md end-to-end deploy runbook covering Turso, Resend, R2, Stripe webhook setup, env vars, init scripts, post-deploy verification, and a Stripe-CLI testing recipe.
- Staging / preview environment guidance added to DEPLOY.md.
Accessibility
- Skip-to-content link in root layout (visible only on keyboard focus).
- Modal primitive (
Modalcomponent) with focus trap, Escape-to-close, body-scroll-lock, ARIA dialog role, restored focus on close.ClaimToolButtonrefactored to use it. - Added
aria-labels androle="alert"to form error messages.
Schema (v2.8)
ALTER TABLE subscribers ADD COLUMN unsubscribed_at INTEGER;
ALTER TABLE posts ADD COLUMN scheduled_for INTEGER;
CREATE TABLE search_queries (
id, query, result_count, ip_hash, created_at
);
Environment (v2.8)
- New:
UNSUBSCRIBE_SECRET(any long random string) - Optional:
SENTRY_DSN(when you wire Sentry) - Optional:
CRON_SECRET(recommended for production)
v2.7 — Launch-readiness hardening + collections + dark mode + scaffolds
Security / integrity (#1–#5)
- Password reset:
/forgot-password+/reset-passwordpages,/api/auth/forgot-password+/api/auth/reset-passwordroutes, 1-hour token, bcrypt-hashed - Email verification:
email_verifiedflag on users, verification email sent on signup,/api/auth/verify-emailendpoint, banner on dashboard with resend button, gates on reviews + tool claims + blog comments - Rate limiting audit: confirmed every critical API (register, vote, submit, subscribe, review, claim, pwreset, comments) routes through
rateLimitOrBlock - Spam protection:
lib/spam.tswith honeypot field + form-timing + keyword/URL filters; wired into submit, subscribe, reviews, comments forms (all forms add hiddennicknameinput andformStartedAttimestamp) - Review moderation: new reviews go to
pending, admin/admin/reviewsqueue with approve/reject. Recompute aggregate on approve
Production polish (#6–#12)
SafeImagecomponent: drop-innext/imagereplacement with graceful fallback to a colored letter placeholder/changelogpublic page readingCHANGELOG.mdand rendering vialib/markdown.ts- Collections: replaced previous stub schema.
/api/collectionsCRUD,/api/collections/toolsadd/remove. DashboardCollectionsManagercomponent with create/delete/toggle public. Public viewer at/u/[userId]/[slug] - Report a tool:
tool_reportstable,/api/tools/report,ReportToolButtonmodal on tool pages,/admin/reportsqueue with resolve/dismiss - Newsletter digest cron:
/api/cron/newslettersends weekly "new tools this week" email to confirmed subscribers. Scheduled invercel.jsonMondays 13:00 UTC - Trending recompute verified — already scheduled every 6h
- Footer links to changelog
UX (#14, #17, #18, #19)
- Dark mode toggle:
DarkModeTogglecomponent (Sun / System / Moon). CSS variables inglobals.cssfor dark scheme. Honorsprefers-color-schemewhen "auto" - Feature filters: API / Mobile / Browser ext. / Team / Self-hosted toggles on
/ai-tools. Readtools.featuresJSON via LIKE substring match - Social proof: tool pages show "X people visited this week" when click count ≥ 5
- User profiles:
/u/[userId]lists a user's public collections
Blog comments (#15) — minimal
post_commentstable. Flat (no replies). Login + email-verified required. Pending by default. Honeypot + spam filter applied. Admin moderation at/admin/comments
Scaffolds (clearly marked incomplete)
#13 i18n — /fr demo page + lib/i18n.ts with English + sample French strings. NOT ready for multi-language production. Needs next-intl, middleware, locale-aware routing, all literal strings replaced with t(), human translation.
#16 Public API — /api/v1/tools endpoint (read-only, key-gated) + api_keys table + /admin/api-keys page to issue keys. NOT ready for public launch. Keys stored plain; no per-key rate limit; no OpenAPI; no CORS; no versioning.
#20 Webhooks — outgoing_webhooks + webhook_deliveries tables + /admin/webhooks viewer. NOT wired to any event emitter; no retry queue; no HMAC signing helper. Build before announcing.
Schema (v2.7)
ALTER TABLE users ADD COLUMN email_verified INTEGER DEFAULT 0;
CREATE TABLE password_resets (token, user_id, expires_at, used_at, created_at);
CREATE TABLE email_verifications (token, user_id, expires_at, used_at, created_at);
CREATE TABLE tool_reports (id, tool_slug, user_id, reason, details, status, created_at, resolved_at);
CREATE TABLE post_comments (id, post_slug, user_id, body, status, created_at);
-- collections + collection_tools recreated with new public-first shape
-- (DROP + CREATE; safe because previous stub table was never used)
-- Scaffolds:
CREATE TABLE api_keys (id, key, owner_user_id, name, last_used_at, created_at, revoked_at);
CREATE TABLE outgoing_webhooks (id, owner_user_id, tool_slug, url, event_type, secret, created_at, disabled_at);
CREATE TABLE webhook_deliveries (id, webhook_id, event_type, payload, status, response_code, attempt_count, created_at, delivered_at);
v2.6 — Blog system with admin CMS + auto-generated drafts
Public blog
/blog— paginated index with featured post, tag filter, author bylines, reading-time estimates/blog/[slug]— post page with: cover image, author byline + bio, table of contents (auto-extracted from H2/H3), markdown body, tag cloud, related tools sidebar ("Tools mentioned"), "Keep reading" section. Sticky TOC on desktop./author/[slug]— author profile with bio, social links, all their posts/blog/rss.xml— RSS feed (also auto-discovered via root<link rel="alternate">)- Tool pages now show From the blog section when posts reference them via
relatedToolSlugs - Blog index, posts, and authors all added to the sitemap with proper priorities
- Article JSON-LD on every post: headline, image, datePublished, dateModified, author (Person), publisher (Organization)
- BreadcrumbList JSON-LD on posts and author pages
- Header + mobile menu + footer all link to /blog
Admin CMS
/admin/posts— list view with publish status, featured flag, author column/admin/posts/newand/admin/posts/[slug]— full editor:
- Title, slug, excerpt (200-char limit), markdown body, cover image, author - Tags (comma-separated) - Related tool slugs (drives "From the blog" on tool pages + sidebar on post page) - SEO overrides (meta title, meta description) - Featured toggle - Publish / Save draft / Delete actions
/admin/authors— author list + inline create form/api/admin/posts(POST/PUT/DELETE) and/api/admin/authors(POST/PUT) — admin-only, role-gated- New admin nav links: Posts, Authors
Markdown rendering
- New
lib/markdown.ts— dependency-free renderer for blog body. Supports:
- Headings (h2-h6 only; h1 reserved for post title) - Paragraphs, bold, italic, inline code - Code blocks with optional language hint - Links (auto-open external in new tab with rel=noopener) - Images (with lazy-loading) - Unordered + ordered lists, blockquotes, horizontal rules
- XSS-safe: escapes user content first, then applies markdown transforms with URL safelist
- Helpers:
readingTime(md)(200 wpm) andextractToc(md)for sidebar
Schema (v2.6)
CREATE TABLE authors (slug, name, bio, avatar_url, twitter, linkedin, website, created_at);
CREATE TABLE posts (
slug, title, excerpt, body, cover_image_url, author_slug,
tags, related_tool_slugs, status, published_at,
created_at, updated_at, meta_title, meta_description, featured
);
Both added to init-db.ts and pipeline/migrations.sql.
Pipeline: gen_blog.py
- Auto-generates 5–10 SEO-targeted blog post drafts and inserts them as
status='draft'(never auto-publishes) - Topic templates cover: category overviews, how-to guides, free-vs-paid comparisons, trend recaps
- For each category, pulls top 15 tools by votes and instructs Claude to mention 3-5 of them naturally with their slugs — auto-linked via the post's
relatedToolSlugs - Default author "Toolglade Editorial" auto-created on first run; override with
--author-slug your-name - Editorial note: AI-generated content alone won't rank well anymore. Review every draft, add personal insights and screenshots, edit ruthlessly before publishing.
Run after gen_bestof.py:
python gen_blog.py --limit 10
# Visit /admin/posts to review and edit
# Set status to 'published' to make them live
v2.5 — Auto affiliate program discovery + A/B testing
Auto-discovery (pipeline)
generate_content.pynow scrapes/affiliate,/affiliates,/affiliate-program,/partners,/partner-program,/referral,/refer,/ambassadors,/programon each tool's domain, looking for signal words (affiliate / commission / payout / referral).- LLM detects programs: extracts the network used (PartnerStack / Impact / Rewardful / FirstPromoter / in-house), commission rate, and cookie window when visible.
- Stored on the tool:
affiliate_program_url(the signup page) andaffiliate_notes(LLM-extracted summary). Only populated when the page text actually describes a program. - Admin "Affiliate opportunities" page (
/admin/affiliates): lists tools with detected programs that don't yet have an affiliate URL set. Sorted by votes — biggest revenue opportunities first. Each row has a direct "Sign up" link to the program page.
A/B testing of affiliate URLs
- New
affiliate_variantsJSON column on tools — an array of{ id, url, weight }. Empty/null means no test, fall through toaffiliate_urlorwebsite_url. /go/[slug]does weighted random pick when variants are configured. Each click is independently sampled. Setweight: 0to pause a variant without deleting it.variantcolumn onclicksrecords which variant was served, so you can compute per-variant click rates (and later, conversion rates once you wire postbacks).- Admin A/B test page (
/admin/ab-tests): per-tool variant breakdown with 30-day click counts, weight, and visual percentage bars. Tells you which variant to keep.
Schema (v2.5)
ALTER TABLE tools ADD COLUMN affiliate_program_url TEXT;
ALTER TABLE tools ADD COLUMN affiliate_notes TEXT;
ALTER TABLE tools ADD COLUMN affiliate_variants TEXT;
ALTER TABLE clicks ADD COLUMN variant TEXT;
All in pipeline/migrations.sql. Safe to re-run.
Workflow
- Run
python generate_content.py(or--reprocessfor existing tools). Pipeline detects programs across your corpus. - Visit
/admin/affiliatesto see unmonetized tools with programs. - Sign up for the most valuable programs (sorted by traffic).
- Either set
affiliate_urlon individual tools, or add a domain rule tolib/affiliate.tsfor whole-network coverage. - To A/B test a tool, set
affiliate_variantsto a JSON array of options. - Check
/admin/ab-testsafter a week or two to compare click rates.
v2.4 — Affiliate system with click tracking
What's new
- Central
/go/[slug]redirect: every "Visit" link on the site routes through here. Resolves to the right URL and logs the click. Tool detail page and compare page now use this; tool cards still link to the internal tool page (correct UX — visitors see your content first). - Click logging table (
clicks): one row per outbound click. Captures: tool, optional user, referrer, country, hashed IP (never raw), user-agent, whether the resolved URL was affiliate, parsed UTM tags. Indexed by tool, time, and affiliate flag. - Affiliate URL resolver (
lib/affiliate.ts): per-network rules with hostname-suffix matching, default UTM tagging, override semantics. EditAFFILIATE_RULESto plug in PartnerStack / Impact / Reditus / FirstPromoter referral codes. Pre-settool.affiliateUrlalways takes priority (admin/pipeline-trusted). - Admin click analytics: dashboard now shows 7-day outbound and affiliate click counters, plus a "Top outbound clicks (30d)" table with affiliate-click breakdown per tool.
- Privacy: IPs are hashed with a salt (
IP_HASH_SALTenv var), not stored raw. Hashes are truncated to 32 chars. Anonymous users haveuser_id = null. - SEO: outbound links use
rel="noreferrer sponsored nofollow", redirect status is 307 (temporary) so search engines don't transfer link equity to partners.
Schema
- New
clickstable (id, tool_slug, user_id, referrer, ip_hash, country, user_agent, is_affiliate, utm_*, created_at). Migration covers it.
Environment
- New
IP_HASH_SALTenv var. Defaults to a placeholder; change before production.
How to add affiliate codes for a partner
Open lib/affiliate.ts and add to AFFILIATE_RULES:
{ hostSuffix: "openai.com", params: { ref: "your-aitools-code" } }
That's it — every outbound click to *.openai.com now goes through your affiliate code. No per-tool edits needed.
v2.3.1 — Mobile/tablet polish
Mobile and tablet experience fixes. No schema changes.
- Viewport meta: added Next 15's
export const viewportwithwidth=device-width, initial-scale=1, maximum-scale=5. Without this, mobile browsers rendered at desktop width and let users zoom — fixed. - Mobile navigation: new
MobileMenudrawer (hamburger → full-screen panel) with nav links, search, and auth CTAs. Body scroll locks while open. Replaces the previously-invisible mobile nav. - Mobile search: now accessible inside the mobile menu drawer.
- Compare tables: tables now have
min-w-[640px]so they horizontally scroll on mobile instead of squishing columns. Edge-to-edge on small screens with-mx-4 sm:mx-0. Added "← Swipe to compare more →" hint. - Sticky sidebar: tool detail sidebar only sticks at
lg:(1024px+) — was wasting vertical space on tablets and mobile. - Touch targets: enlarged tap targets on cookie banner "Customize" button, dashboard owned-tool action buttons. All key buttons now meet the 44px iOS HIG minimum.
- Owned-tool row layout: stacks vertically below 640px so Featured/Premium/View buttons don't wrap awkwardly.
v2.3 — Legal pages, Stripe paid tiers, featured-first sorting, GDPR tools
Tier 1 — Legal & company pages
- New pages:
/about,/contact,/privacy,/terms,/cookies,/advertise,/dmca - Shared
LegalLayoutcomponent with prose styling (requires@tailwindcss/typography) - Cookie consent banner (
CookieBanner) with 3 categories: necessary / analytics / marketing - Footer expanded with Discover / Company / Legal columns
- Privacy and Terms include template-disclaimer banners reminding you to have a lawyer review
Tier 2 — Stripe paid tiers
- 3 tiers defined in
lib/billing.ts: Listed (free) / Featured ($49/mo) / Premium ($149/mo) - Schema additions:
- tools: ownerUserId, subscriptionTier, subscriptionStatus, stripeCustomerId, stripeSubscriptionId, subscriptionRenewsAt - new subscriptions table (mirror of Stripe state, updated by webhooks) - new tool_claims table (email-domain verification for tool ownership)
- API routes:
- POST /api/stripe/checkout — creates a Stripe Checkout session for a verified tool owner - POST /api/stripe/webhook — mirrors subscription events into subscriptions and updates the matching tools row - POST /api/stripe/portal — Stripe-hosted billing portal (cancel/update card/invoices) - POST /api/claim — starts tool-ownership verification via domain email - GET /api/claim/verify — completes verification, sets owner_user_id
- UI:
- /pricing page with side-by-side tier cards + FAQ - "Claim this tool" button + modal on tool detail page - Dashboard "Your tools" section with upgrade and manage-billing buttons - Tier badges (Featured / Premium) on tool cards and tool pages
Site-wide featured-first sorting
/ai-tools,/category/[slug], and/api/searchapply aCASEboost (premium > featured > free) before the user's chosen sort- ToolCard renders distinct Premium (brand-color) vs Featured (accent-color) badges
Tier 3 — Quality of life
- Consent-gated analytics: Plausible and GA4 now load only after user opts in via the cookie banner
- RSS feed at
/new/rss.xmlwith auto-discovery<link rel="alternate">in the root layout - Custom
/not-found.tsxand/error.tsxpages - GDPR data tools:
- GET /api/me/export — JSON dump of all personal data - POST /api/me/delete — full account deletion with confirm: "DELETE" check; refuses if active paid subscription exists - PrivacyControls component on dashboard
Dependencies
- Added
stripe@^17.5.0(runtime) - Added
@tailwindcss/typography@^0.5.15(dev) — used byLegalLayout
Environment variables (new)
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETSTRIPE_PRICE_FEATURED,STRIPE_PRICE_PREMIUMNEXT_PUBLIC_CONTACT_EMAIL,NEXT_PUBLIC_PARTNERSHIPS_EMAIL,NEXT_PUBLIC_DMCA_EMAILNEXT_PUBLIC_GA_ID(optional alongside Plausible)- See
.env.examplefor the full list.
Migration
Run once on existing databases:
sqlite3 local.db < pipeline/migrations.sql
Adds the new columns and creates subscriptions and tool_claims tables.
Stripe setup (one-time)
npm install(picks up the newstripepackage)- Create two recurring prices in Stripe Dashboard: Featured ($49/mo) and Premium ($149/mo). Paste the Price IDs into
STRIPE_PRICE_FEATUREDandSTRIPE_PRICE_PREMIUM. - Add a webhook in Stripe Dashboard:
- URL: {NEXT_PUBLIC_SITE_URL}/api/stripe/webhook - Events: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, checkout.session.completed - Copy the signing secret into STRIPE_WEBHOOK_SECRET.
v2.2 — Features, alternatives, best-of, use cases, dynamic theme, SEO
Discovery features
- Side-by-side feature comparison on
/compare - Alternatives page
/tool/[slug]/alternatives - Smart category pages with hero, top 3 picks, FAQ
- Best-of landing pages
/best/[slug]populated bygen_bestof.py - Use case directory
/use-cases+/use-case/[slug] - What's new feed
/new - Live search dropdown with keyboard nav
Theming
- All
brand-andaccent-Tailwind colors read from CSS variables inapp/globals.css - See
THEMING.mdfor examples
SEO
- Sitemap split with
generateSitemaps - HowTo JSON-LD on tool pages, FAQPage JSON-LD on category pages
Performance
- Migrated all
<img>tonext/image
Pipeline
featurescolumn added; pipeline LLM generates structured feature flagsgen_bestof.pyscript auto-generates best-of pages
v2.1 — Data pipeline + extended categories + richer tool pages
- Python pipeline (Product Hunt + Claude enrichment)
- 10 new categories (20 total)
extra_contentJSON column (how-to, use cases, pros, cons)- Three new tool detail sections