Skip to main content
What's new

Changelog

Recent product updates. Subscribe to the RSS feed or check the blog for deep dives.

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.

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-auto wrapper + 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.

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.

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).
  • /deals page + deals table + /admin/deals viewer: 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. New alternative_votes table with vote tallies surfaced on the alternatives page as a "Voted by the community" leaderboard above the standard grid.
  • /newsletter archive index + /newsletter/[slug] issue pages. Cron writes each issue to a new newsletter_issues table for permanent indexable archives.
  • /tools/prompt-generator free interactive utility with full <PromptBuilder> component (role/task/context/format/constraints structured prompt builder, copy-to-clipboard). Plus /tools hub for future utilities.
  • /top-100 leaderboard 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.ts truncate()): word-boundary truncation. Tool pages prefer metaDescription if editorially set, else truncate description.
  • 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_reports queue 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 a requestId, 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.
  • HttpError class: routes throw new 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, /blog so 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.ts generated with commented-out Sentry.init() calls.
  • lib/observability.ts updated with dynamic import("@sentry/nextjs") pattern that activates when both SENTRY_DSN and NEXT_PUBLIC_SENTRY_DSN are set.
  • @sentry/nextjs added 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)

  • Unsubscribe flow (/api/unsubscribe): HMAC-token validation, RFC 8058 one-click POST, plain HTML success page. Newsletter cron injects per-recipient unsub URL + List-Unsubscribe and List-Unsubscribe-Post headers 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/health for uptime monitors — returns 200/503 with DB ping latency.
  • Error tracking stub (lib/observability.ts): wired into app/error.tsx, drops to console; uncomment Sentry imports when ready.
  • Backup script (npm run db:backup): dumps all 25 tables to backups/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 /help with FAQPage JSON-LD.

SEO / discoverability

  • Visible breadcrumbs (Breadcrumbs component) 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): keep unoptimized for unknown external sources, but optimize images served from your own R2 bucket via shouldOptimize() / imageProps().

Editorial workflow

  • Scheduled publishing for blog posts: new posts.scheduledFor column, scheduled status. New /api/cron/publish-scheduled runs every 15 minutes via vercel.json and flips posts to published when 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 (Modal component) with focus trap, Escape-to-close, body-scroll-lock, ARIA dialog role, restored focus on close. ClaimToolButton refactored to use it.
  • Added aria-labels and role="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-password pages, /api/auth/forgot-password + /api/auth/reset-password routes, 1-hour token, bcrypt-hashed
  • Email verification: email_verified flag on users, verification email sent on signup, /api/auth/verify-email endpoint, 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.ts with honeypot field + form-timing + keyword/URL filters; wired into submit, subscribe, reviews, comments forms (all forms add hidden nickname input and formStartedAt timestamp)
  • Review moderation: new reviews go to pending, admin /admin/reviews queue with approve/reject. Recompute aggregate on approve

Production polish (#6–#12)

  • SafeImage component: drop-in next/image replacement with graceful fallback to a colored letter placeholder
  • /changelog public page reading CHANGELOG.md and rendering via lib/markdown.ts
  • Collections: replaced previous stub schema. /api/collections CRUD, /api/collections/tools add/remove. Dashboard CollectionsManager component with create/delete/toggle public. Public viewer at /u/[userId]/[slug]
  • Report a tool: tool_reports table, /api/tools/report, ReportToolButton modal on tool pages, /admin/reports queue with resolve/dismiss
  • Newsletter digest cron: /api/cron/newsletter sends weekly "new tools this week" email to confirmed subscribers. Scheduled in vercel.json Mondays 13:00 UTC
  • Trending recompute verified — already scheduled every 6h
  • Footer links to changelog

UX (#14, #17, #18, #19)

  • Dark mode toggle: DarkModeToggle component (Sun / System / Moon). CSS variables in globals.css for dark scheme. Honors prefers-color-scheme when "auto"
  • Feature filters: API / Mobile / Browser ext. / Team / Self-hosted toggles on /ai-tools. Read tools.features JSON 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_comments table. 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 Webhooksoutgoing_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/new and /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) and extractToc(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.py now scrapes /affiliate, /affiliates, /affiliate-program, /partners, /partner-program, /referral, /refer, /ambassadors, /program on 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) and affiliate_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_variants JSON column on tools — an array of { id, url, weight }. Empty/null means no test, fall through to affiliate_url or website_url.
  • /go/[slug] does weighted random pick when variants are configured. Each click is independently sampled. Set weight: 0 to pause a variant without deleting it.
  • variant column on clicks records 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

  1. Run python generate_content.py (or --reprocess for existing tools). Pipeline detects programs across your corpus.
  2. Visit /admin/affiliates to see unmonetized tools with programs.
  3. Sign up for the most valuable programs (sorted by traffic).
  4. Either set affiliate_url on individual tools, or add a domain rule to lib/affiliate.ts for whole-network coverage.
  5. To A/B test a tool, set affiliate_variants to a JSON array of options.
  6. Check /admin/ab-tests after 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. Edit AFFILIATE_RULES to plug in PartnerStack / Impact / Reditus / FirstPromoter referral codes. Pre-set tool.affiliateUrl always 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_SALT env var), not stored raw. Hashes are truncated to 32 chars. Anonymous users have user_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 clicks table (id, tool_slug, user_id, referrer, ip_hash, country, user_agent, is_affiliate, utm_*, created_at). Migration covers it.

Environment

  • New IP_HASH_SALT env 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 viewport with width=device-width, initial-scale=1, maximum-scale=5. Without this, mobile browsers rendered at desktop width and let users zoom — fixed.
  • Mobile navigation: new MobileMenu drawer (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.
  • New pages: /about, /contact, /privacy, /terms, /cookies, /advertise, /dmca
  • Shared LegalLayout component 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

  • /ai-tools, /category/[slug], and /api/search apply a CASE boost (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.xml with auto-discovery <link rel="alternate"> in the root layout
  • Custom /not-found.tsx and /error.tsx pages
  • 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 by LegalLayout

Environment variables (new)

  • STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
  • STRIPE_PRICE_FEATURED, STRIPE_PRICE_PREMIUM
  • NEXT_PUBLIC_CONTACT_EMAIL, NEXT_PUBLIC_PARTNERSHIPS_EMAIL, NEXT_PUBLIC_DMCA_EMAIL
  • NEXT_PUBLIC_GA_ID (optional alongside Plausible)
  • See .env.example for 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)

  1. npm install (picks up the new stripe package)
  2. Create two recurring prices in Stripe Dashboard: Featured ($49/mo) and Premium ($149/mo). Paste the Price IDs into STRIPE_PRICE_FEATURED and STRIPE_PRICE_PREMIUM.
  3. 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 by gen_bestof.py
  • Use case directory /use-cases + /use-case/[slug]
  • What's new feed /new
  • Live search dropdown with keyboard nav

Theming

  • All brand- and accent- Tailwind colors read from CSS variables in app/globals.css
  • See THEMING.md for examples

SEO

  • Sitemap split with generateSitemaps
  • HowTo JSON-LD on tool pages, FAQPage JSON-LD on category pages

Performance

  • Migrated all <img> to next/image

Pipeline

  • features column added; pipeline LLM generates structured feature flags
  • gen_bestof.py script 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_content JSON column (how-to, use cases, pros, cons)
  • Three new tool detail sections