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.

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-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.

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).
  • /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)

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-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.

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 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

Site-wide featured-first sorting

  • /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