SecureStartKit
SecurityFeaturesPricingDocsBlogChangelog
Sign inBuy Now

Changelog

See what's new in SecureStartKit

May 15, 2026·v1.8.4

Server-Side getUser() Migrated to Local JWT Validation

securitynew

What Changed

lib/supabase/server.ts:getUser() now uses supabase.auth.getClaims() instead of supabase.auth.getUser(). Every Server Action that identifies the caller (billing, admin, user settings, profile updates) now validates the JWT signature locally against Supabase's cached asymmetric public key set, instead of making a network round trip to the Supabase auth API.

The proxy-side rollout in v1.8.3 covered the route guard. This release extends the same pattern to the per-action identity check, which is where most of the cumulative latency lives. A typical login + dashboard load + a couple of settings clicks involves 5 to 10 Server Action calls. At 50 to 200ms per round trip depending on cross-region pairing, that's roughly 250ms to 2 seconds saved per user session.

What Ships in the Template

getUser() returns a normalized identity type

The return type changes from Supabase's full User object to a normalized AuthUser containing only what the template's callsites actually read:

export type AuthUser = {
  id: string
  email: string
}

export async function getUser(): Promise<AuthUser | null> {
  const supabase = await createServerClientWithCookies()
  const { data } = await supabase.auth.getClaims()
  const claims = data?.claims
  if (!claims || !claims.email) return null
  return {
    id: claims.sub,
    email: claims.email,
  }
}

The 5 callsites in the template (actions/admin.ts, actions/billing.ts (2 places), actions/user.ts, app/(dashboard)/billing/page.tsx) only ever read user.id and user.email, so no callsite changes were needed.

One downstream prop type changed: components/layout/dashboard-header.tsx previously typed its user prop as User from @supabase/supabase-js; it now types it as AuthUser from lib/supabase/server.

Breaking Change Note

If your project has customized Server Actions that read fields beyond user.id and user.email (for example user.user_metadata, user.app_metadata, user.created_at, user.phone), they will fail to compile after this change. Two paths to fix:

  1. Extend the AuthUser type in lib/supabase/server.ts and read the additional fields from claims in getUser(). Most JWT-resident fields (role, phone, app_metadata, user_metadata) are available on the claims object per the JwtPayload type in @supabase/auth-js.
  2. For fields not in the JWT claims (like created_at), pull them from the profiles table via getUserWithProfile() or a fresh admin client query.

Prerequisites

Asymmetric JWT signing keys must be enabled on the Supabase project. New projects ship with them by default since 2025; existing projects enable them in the Supabase dashboard under Authentication → Keys.

What Did NOT Change

  • getUserWithProfile() unchanged; it just calls the new getUser() and queries the profile row by user.id.
  • The cookie-based session client (createServerClientWithCookies) unchanged.
  • The admin client (createAdminClient) unchanged.
  • next.config.ts unchanged.
  • Marketing/blog/login routes still use the next.config.ts baseline CSP; the strict nonce-based CSP introduced in v1.8.3 still covers only /dashboard, /settings, /billing, /admin.

Upgrading Existing Projects

Replace the getUser() body in lib/supabase/server.ts with the getClaims() version above, add the AuthUser type export, and update any component that types its user prop as the Supabase User to use AuthUser instead. Run npm run typecheck to catch any callsite that reads fields beyond id and email; extend AuthUser for those.

May 15, 2026·v1.8.3

Nonce-Based CSP on Auth Routes and Local JWT Validation

securitynew

What This Defends Against

Two upgrades to the auth-gated dashboard area.

First, the dashboard, settings, billing, and admin routes previously relied on the same CSP as marketing pages: strict directives but with 'unsafe-inline' in script-src as a documented compromise for Next.js hydration scripts and shadcn/ui. 'unsafe-inline' is the canonical XSS escape hatch. An attacker who manages to inject a <script> tag into a Server Component's rendered output (via an unsanitized prop, a Markdown bypass, or React's raw-HTML escape hatch) gets script execution despite every other defense. The fix on routes that handle authenticated user data is nonce-based CSP with 'strict-dynamic'.

Second, the proxy was calling supabase.auth.getUser() on every protected-route request, which hits the Supabase auth API over the network. With asymmetric JWT signing keys (the default for Supabase projects since 2025), getClaims() verifies the JWT locally against the cached public key set instead. Faster, fewer cross-region round trips, and the Supabase-recommended path going forward.

What Ships in the Template

Scoped nonce-based CSP for dynamic auth routes

proxy.ts now generates a per-request nonce and emits a strict CSP for these route prefixes:

  • /dashboard
  • /settings
  • /billing
  • /admin

The CSP drops 'unsafe-inline' from script-src and adds 'nonce-${nonce}' 'strict-dynamic'. Modern browsers ignore the absence of 'unsafe-inline' once 'strict-dynamic' is present and only execute scripts that carry the matching per-request nonce. Next.js automatically stamps the nonce into its hydration scripts during render when the CSP header is set on the incoming request.

// proxy.ts (abbreviated)
const strictCspRoutePrefixes = [
  '/dashboard',
  '/settings',
  '/billing',
  '/admin',
]

const useStrictCsp = strictCspRoutePrefixes.some((p) => path.startsWith(p))
const nonce = useStrictCsp
  ? Buffer.from(crypto.randomUUID()).toString('base64')
  : null

Marketing pages, blog posts, login, signup, and reset-password stay on the next.config.ts CSP because they may be statically pre-rendered. Applying per-request nonces to those routes would break their inline hydration scripts (the build-time nonce wouldn't match the runtime CSP). The current next.config.ts CSP with 'unsafe-inline' is the safest baseline for SSG pages until a separate hash-based or full-dynamic migration ships.

style-src keeps 'unsafe-inline' on the strict routes too. shadcn/ui generates inline style="..." attributes; the accepted compromise is that inline styles cannot execute scripts, so the XSS attack surface from styles is much smaller than from inline <script> tags.

Local JWT validation via getClaims()

proxy.ts now uses supabase.auth.getClaims() instead of supabase.auth.getUser(). The new call validates the JWT signature against Supabase's cached asymmetric public key set, falling back to a network call only when the key set isn't cached. The admin email check reads claims.email (typed string | undefined per JwtPayload) with a defensive null guard.

const { data: claimsData } = await supabase.auth.getClaims()
const claims = claimsData?.claims ?? null
// ...
if (
  !appConfig.admin.enabled ||
  !claims.email ||
  !appConfig.admin.superAdminEmails.includes(claims.email)
) {
  return redirect('/dashboard')
}

Existing projects need asymmetric JWT signing keys enabled in the Supabase dashboard. New Supabase projects ship with them by default since 2025.

What Did NOT Change

  • The next.config.ts CSP that covers marketing, blog, login, signup, reset-password, and other static routes still uses 'unsafe-inline' in script-src. A full migration to nonce-based or hash-based CSP for those routes is a separate refactor (requires either making the pages dynamic, which costs SSG, or computing per-build hashes for every inline script).
  • Server-side helpers like lib/supabase/server.ts:getUser still use getUser(). Server Actions that call it continue to hit the network for validation. Migrating those is a separate change after this proxy-side rollout has soaked.

Upgrading Existing Projects

Copy the strictCspRoutePrefixes block, the buildStrictCsp helper, and the getClaims() swap from the template's proxy.ts into your project. Run npm run typecheck and npm run build to confirm. The proxy matcher does not change.

If you have additional auth-gated route prefixes beyond the template defaults, add them to strictCspRoutePrefixes. Anything you add must be a dynamic route (Server Component reading cookies, headers, or user data); applying nonce-based CSP to a statically rendered route will break its inline hydration scripts.

May 15, 2026·v1.8.2

Stricter CSP and Server Action CSRF for Proxy Deployments

securitynew

What This Defends Against

Two small holes the template's defaults left open.

First, the Content Security Policy shipped without object-src 'none' or upgrade-insecure-requests. The omission left legacy plugin content technically allowed and let HTTP subresources stay HTTP even on HTTPS pages. Neither is an exploitable vulnerability on its own, but both are explicit recommendations in the 3-layer injection defense guide.

Second, Server Action CSRF protection works automatically on Vercel because Vercel forwards X-Forwarded-Host correctly. Behind a custom proxy (Cloudflare Tunnel, a custom load balancer, a self-hosted reverse proxy), the Host header may not match the public origin, and Server Action POSTs return 403 in production. The fix is experimental.serverActions.allowedOrigins listing the trusted host.

What Ships in the Template

next.config.ts CSP additions

Two new directives in the Content-Security-Policy header:

key: 'Content-Security-Policy',
value: [
  // ... existing directives ...
  "object-src 'none'",
  'upgrade-insecure-requests',
].join('; ')

object-src 'none' blocks Flash and other legacy plugin content. Modern apps should never need it; the directive enforces that.

upgrade-insecure-requests forces every HTTP subresource (images, scripts, fonts) to upgrade to HTTPS. Belt-and-suspenders with HSTS, which the template already ships.

serverActions.allowedOrigins from NEXT_PUBLIC_APP_URL

A small getAppHost() helper parses NEXT_PUBLIC_APP_URL and supplies the host to experimental.serverActions.allowedOrigins. If the env is unset, the config is omitted entirely (no breakage on fresh clones).

function getAppHost(): string | null {
  const raw = process.env.NEXT_PUBLIC_APP_URL
  if (!raw) return null
  try {
    return new URL(raw).host
  } catch {
    return null
  }
}
const appHost = getAppHost()

const nextConfig: NextConfig = {
  ...(appHost
    ? {
        experimental: {
          serverActions: {
            allowedOrigins: [appHost, `*.${appHost}`],
          },
        },
      }
    : {}),
  // ... rest of config
}

Set NEXT_PUBLIC_APP_URL=https://yourdomain.com in .env.local (you already have this for the rest of the template) and the allowlist tracks it automatically.

What Did NOT Change

The CSP still uses 'unsafe-inline' in script-src for Next.js hydration scripts and shadcn/ui styles. The nonce-based migration that fully eliminates inline scripts is real refactor work and a separate release. The blog post walks the recommended endpoint at Next.js CSRF, XSS, SQLi: the 3-layer defense.

Upgrading Existing Projects

Copy the two CSP directives and the getAppHost() block into your project's next.config.ts. No dependency changes, no lockfile changes, no breaking changes. Run npm run typecheck to confirm.

May 12, 2026·v1.8.1

Supply-Chain Hardening: 7-Day Version Quarantine

securitynew

What This Defends Against

On May 11 the TanStack npm packages were compromised: 84 malicious versions across 42 @tanstack/* packages were published in a 6-minute window. The same playbook (the "Mini Shai-Hulud" worm) hit axios in March and Mistral, UiPath, and Guardrails packages in between. Each attack window is roughly 2 to 12 hours before npm yanks the bad versions.

Anyone who ran npm install against a ^ range during that window pulled poisoned code.

What Ships in the Template

A new .npmrc is now part of the template root with two settings:

min-release-age=7
save-exact=true

min-release-age=7

npm refuses to install any package version published within the last 7 days. If a malicious release lands and gets yanked within a week (which is what happens in practice), your install never sees it. The version stays invisible to you until it has survived a full week in the wild.

Requires npm 11.10 or later. Run npm install -g npm@latest if you are on an older release.

save-exact=true

When you run npm install <pkg>, the template writes "<pkg>": "1.2.3" to package.json instead of "<pkg>": "^1.2.3". This stops silent patch bumps to compromised versions and makes your package.json an honest record of what is actually installed.

Escape Hatches

If you genuinely need a fresh release for a one-off:

npm install some-package --min-release-age=0

Or temporarily edit .npmrc to set min-release-age=0 and revert when done.

What Changed

  • .npmrc added at template root (12 lines, mostly comments)
  • No code changes, no dependency changes, no lockfile changes
  • Existing projects built from earlier template versions: copy .npmrc into your repo root and upgrade npm

Why Project-Level, Not User-Level

You can also put these settings in ~/.npmrc to cover every project on your machine. The template ships them at the project level so:

  1. CI runners inherit the policy from the repo, not the developer machine
  2. Anyone cloning the template gets protected on first npm install
  3. The protection is auditable in version control
Apr 6, 2026·v1.8.0

Centralized Landing Content & CSP Dev Fix

improvedfixed

One File to Rebrand the Entire Landing Page

All landing page copy now lives in a single file: content/landing.ts. Hero taglines, feature descriptions, testimonials, FAQ items, CTA text, and footer links - everything a buyer needs to change before shipping is in one place.

Before

Copy was scattered across 7 component files (Hero.tsx, Features.tsx, Testimonials.tsx, Pricing.tsx, FAQ.tsx, CTA.tsx, Footer.tsx) in inline const blocks and hardcoded JSX strings. Rebranding meant hunting through every component.

After

Components are now pure presentation. They import from content/landing.ts and render whatever's there. To rebrand:

  1. Edit content/landing.ts (all marketing copy)
  2. Edit config.ts (app name, pricing plans, SEO defaults)
  3. Done. Zero component edits needed.

Type-safe Icon Picker

Feature icons are referenced by string key (e.g., icon: 'lock') instead of requiring lucide-react imports. A shared _icons.ts map ships with 20 common SaaS icons and provides autocomplete via the IconKey type. Lucide is tree-shaken, so unused icons cost zero bundle bytes.

Available icons: lock, credit-card, mail, database, shield, zap, sparkles, rocket, users, bar-chart, globe, bell, message-square, calendar, file-text, cloud, settings, heart, star, check-circle.

Self-Invalidating Demo Content

The placeholder copy is written to look professional but talks about the template itself ("This template saved me weeks of work", "Fork it, rebrand it, ship it"). This means:

  • First clone looks polished, not like a skeleton
  • Buyers can't accidentally ship placeholder copy because it obviously doesn't describe their product
  • A warning comment at the top of content/landing.ts reminds buyers to replace everything before production

CSP Fix for Development Mode

React 19's development build requires dynamic code execution for Fast Refresh and stack reconstruction, which the Content Security Policy header was blocking. The CSP now conditionally relaxes script-src only when NODE_ENV=development. Production CSP is unchanged and remains strict.

What Changed

  • content/landing.ts added (all landing copy, typed)
  • components/landing/_icons.ts added (20-icon map with IconKey type)
  • components/landing/Hero.tsx updated (reads from content)
  • components/landing/Features.tsx updated (reads from content, uses icon map)
  • components/landing/Testimonials.tsx updated (reads from content)
  • components/landing/Pricing.tsx updated (6 hardcoded strings extracted)
  • components/landing/FAQ.tsx updated (reads from content)
  • components/landing/CTA.tsx updated (reads from content)
  • components/landing/Footer.tsx updated (reads from content)
  • components/landing/faq-data.ts deleted (absorbed into content/landing.ts)
  • lib/seo.ts updated (FAQ import path)
  • next.config.ts updated (dev-only CSP relaxation)
Apr 2, 2026·v1.7.1

SEO Defaults & Audit Fixes

improved

Better SEO Out of the Box

Every new project built from the template now starts with production-grade SEO defaults. These fixes were identified during a full audit of securestartkit.com and ported back into the template.

New Files

  • app/icon.tsx - Dynamic favicon generated from your app name (first letter). No need to create image files manually.
  • app/logo.png/route.tsx - Dynamic logo image at /logo.png for Organization schema. Resolves the broken logo reference in structured data.
  • app/llms.txt/route.ts - AI crawler guidance file. Describes your product, lists key pages, and includes recent blog posts. Improves visibility in ChatGPT, Perplexity, and Google AI Overviews.

Meta Description Fixes

  • Privacy page - Added description (was missing entirely)
  • Terms page - Added description (was missing entirely)
  • Pricing page - Rewritten from generic "Simple, transparent pricing" to keyword-rich description
  • Blog page - Rewritten from generic "Read our latest articles and updates" to dynamic description using app name

Schema Fixes

  • Article author type - Changed from "@type": "Person" to "@type": "Organization" with URL. The previous setup incorrectly used Person type for an organization name.

Sitemap Improvements

  • Static page timestamps - Changed from new Date() (rebuild timestamp) to fixed dates. Google downgrades lastmod reliability when all pages share the same timestamp.
  • Category page timestamps - Now derived from the most recent post in each category instead of using the build timestamp.

RSS Autodiscovery

  • Added <link rel="alternate" type="application/rss+xml"> to root layout head. Feed readers can now automatically discover your blog's RSS feed.

What Changed

  • app/icon.tsx added (new file)
  • app/logo.png/route.tsx added (new file)
  • app/llms.txt/route.ts added (new file)
  • app/layout.tsx updated (RSS link)
  • app/sitemap.ts updated (lastmod logic)
  • lib/seo.ts updated (author type)
  • Meta descriptions added/improved on 4 pages
Mar 31, 2026·v1.7.0

Major Dependency Upgrade

improved

All Dependencies Updated to Latest Stable

Every core dependency has been upgraded to its latest stable version. The template now ships with the most modern stack available.

Framework & Runtime

  • Next.js 15 → 16.2 — proxy.ts replaces middleware.ts (runs on Node.js runtime, not Edge), Turbopack is the default bundler
  • React 19.0 → 19.2 — latest stable with performance improvements
  • TypeScript 5.7 — unchanged, already latest

Styling

  • Tailwind CSS 3.4 → 4.2 — CSS-first configuration via @theme directive, tailwind.config.ts removed, autoprefixer built-in
  • Framer Motion 11 → Motion 12 — package renamed from framer-motion to motion, imports changed to motion/react

Validation

  • Zod 3.24 → 4.3 — .error.errors renamed to .error.issues throughout the codebase

Auth & Database

  • @supabase/supabase-js 2.47 → 2.101 — latest stable
  • @supabase/ssr 0.5 → 0.10 — latest stable

Payments

  • Stripe 20 → 21 — API version updated to 2026-03-25.dahlia
  • @stripe/stripe-js 5 → 9 — latest client-side SDK

Content & Email

  • Resend 4 → 6.10 — latest email delivery SDK
  • @react-email/components 0.0.31 → 1.0.10 — hit v1.0 stable
  • Lucide React 0.469 → 1.7 — latest icons

i18n

  • next-intl 3.25 → 4.8 — config moved from i18n.ts to i18n/request.ts

What Changed

  • middleware.ts renamed to proxy.ts (Next.js 16 convention)
  • tailwind.config.ts removed — theme config now lives in globals.css via @theme directive
  • postcss.config.mjs updated to use @tailwindcss/postcss
  • All framer-motion imports changed to motion/react
  • All .error.errors changed to .error.issues (Zod 4)
  • Stripe API version updated to 2026-03-25.dahlia
  • i18n.ts moved to i18n/request.ts (next-intl 4 convention)
  • Fixed typo in breadcrumbs.tsx (aimport → import)

Migration Guide (for existing users)

If you're upgrading from v1.6.x:

  1. Rename middleware.ts → proxy.ts and rename the exported function from middleware to proxy
  2. Delete tailwind.config.ts — theme config is now in globals.css under @theme {}
  3. Update postcss.config.mjs to use @tailwindcss/postcss instead of tailwindcss + autoprefixer
  4. Find-and-replace from 'framer-motion' → from 'motion/react'
  5. Find-and-replace .error.errors → .error.issues (Zod validation)
  6. Move i18n.ts → i18n/request.ts
  7. Update Stripe apiVersion to '2026-03-25.dahlia' in lib/stripe/client.ts
  8. Run npm install to pull the updated package.json
Mar 23, 2026·v1.6.1

Rate Limiting Security Fix

fixed

Rate Limiting Now Uses Per-Caller Keys

Auth and billing Server Actions previously used shared global keys for rate limiting. This meant one user hitting the limit would block all other users from logging in or signing up during that window — an accidental self-inflicted lockout.

Rate limits now key by the caller's identity:

  • Login, signup, password reset — keyed by IP address. Each visitor gets their own counter. One attacker hitting the limit does not affect anyone else.
  • Checkout, billing portal — keyed by user ID. Authenticated actions use a stable, non-spoofable identifier instead of IP.

Billing actions (createCheckoutSession, createPortalSession) also had no rate limiting at all. Both are now protected.

What Changed

  • actions/auth.ts — login, signup, resetPassword now use login:${ip}, signup:${ip}, reset:${ip} keys
  • actions/billing.ts — added rate limiting to createCheckoutSession (10/min per user) and createPortalSession (10/min per user)
Mar 19, 2026·v1.6.0

Improved Blog Skill

improved

/write-blog Skill Upgraded to v3.2.0

The blog writing skill received several improvements:

  • Fact verification — all statistics must be verified by fetching the actual source page before citing
  • Natural writing rhythm — varies sentence length, uses contractions, breaks parallel structure to avoid AI-sounding output
  • Post-save verification — automatically checks for em-dashes, calculates read time, validates excerpt length, and verifies internal links
  • Post-publish reminders — conditionally reminds about sitemap, RSS, and structured data updates

What Changed

  • Upgraded /write-blog skill from v3.0.0 to v3.2.0
Mar 14, 2026·v1.5.0

Claude Code Skills - AI-Powered Growth Commands

newimproved

Claude Code Skills

SecureStartKit now ships with 4 built-in Claude Code skills - slash commands that automate content creation, SEO, and brand strategy directly from your terminal.

New Skills

/write-blog - AI Blog Writer

Research keywords by search volume and difficulty, then generate full SEO-optimized blog posts with proper frontmatter, internal links, JSON-LD, and citations. Picks the best keyword opportunity or writes on a topic you choose.

/comparison-page - Competitor Comparison Pages

Build high-converting "[Product] vs [Competitor]" pages. Researches the competitor, generates feature comparisons, and creates SEO-optimized pages with structured data.

/brand-frame - Brand Strategy Framework

Define your brand's strategic frame using the Controlled Exposure Formula. Audits existing pages for brand consistency and outputs a comprehensive brand strategy document.

/weekly-free-tool - Free Tool Page Builder

Find keyword opportunities, research existing tools in your project, and build new free tool pages targeting high-value keywords. Includes JSON-LD, FAQ sections, and "How to use" guides.

How It Works

Skills live in .claude/skills/ and are available as slash commands in Claude Code. Type /write-blog in your terminal and Claude handles the rest - keyword research, content generation, proper formatting, SEO optimization, and file creation.

What Changed

  • Added .claude/skills/write-blog/SKILL.md
  • Added .claude/skills/brand-frame/SKILL.md
  • Added .claude/skills/comparison-page/SKILL.md
  • Added .claude/skills/weekly-free-tool/SKILL.md
  • Upgraded write-blog skill with improved keyword research and SEO scoring
Mar 13, 2026·v1.4.0

GEO optimization for AI search citations

feature

GEO: AI citation optimization

Every project built on SecureStartKit is now optimized for Generative Engine Optimization (GEO), so your public pages get cited by AI search engines like ChatGPT, Claude, Perplexity, and Gemini.

  • toolPageJsonLd() helper in lib/seo.ts generates WebApplication + BreadcrumbList + FAQPage structured data for free tool pages in a single call
  • GEO rules in CLAUDE.md ensure every new page you build follows AI citation best practices: JSON-LD, H1/H2 heading structure, FAQ sections with self-contained answers, and "last updated" timestamps
  • Blog GEO rules added to the /write-blog skill: question-based titles, citable data points, comprehensive coverage, and clear definitions
Mar 13, 2025·v1.4.0

Stripe SDK v20 + Clover API

breakingimproved

Stripe SDK upgraded to v20

Upgraded from Stripe Node SDK v17 to v20 with the 2026-02-25.clover API version. This is the latest major Stripe API version and includes breaking changes to the webhook payload structure.

Breaking: Subscription period fields moved

In the Stripe clover API version, current_period_start and current_period_end moved from the top-level Subscription object to each SubscriptionItem.

Before (acacia API):

const periodStart = subscription.current_period_start
const periodEnd = subscription.current_period_end

After (clover API):

const item = subscription.items.data[0]
const periodStart = item.current_period_start
const periodEnd = item.current_period_end

The webhook handler has been updated to read from the correct location.

Breaking: Invoice subscription field moved

invoice.subscription no longer exists. Use invoice.parent.subscription_details.subscription instead.

What Changed

  • Upgraded stripe package from ^17.0.0 to ^20.0.0
  • Updated lib/stripe/client.ts API version to 2026-02-25.clover
  • Updated app/api/webhooks/stripe/route.ts to read period fields from SubscriptionItem
Mar 9, 2025·v1.3.0

Smarter Blog Generation

improved

Source Enrichment for Blog Posts

The blog generation pipeline now deep-reads cited source URLs before passing research to Gemini. This means Gemini sees the actual article content instead of relying on Perplexity's summary alone, producing posts with verified data points instead of fabricated statistics.

  • Source enrichment step fetches up to 5 source URLs in parallel, extracts real page titles and body text
  • Real citation titles instead of generic "Source from domain.com"
  • Source data passed to Gemini as ground truth for facts and statistics

Fact Verification Rule

Added a critical instruction to the Gemini prompt preventing it from inventing plausible-sounding statistics. Every number, percentage, or data point must now come directly from the provided research or source data.

Lower Temperature

Gemini temperature lowered from 0.7 to 0.3 for blog generation. Lower temperature keeps the model closer to provided source material, reducing hallucination in factual content.

Gemini Model Upgrade

Upgraded the blog generation model from gemini-2.0-flash to gemini-3-pro-preview for higher quality output.

What Changed

  • Added scripts/blog/source-enrichment-service.ts (new file)
  • Updated scripts/blog/perplexity-service.ts to return citation URLs
  • Updated scripts/blog/generate-post.ts with Step 1.5 source enrichment
  • Updated scripts/blog/gemini-service.ts with lower temperature, source data input, and fact verification rule
Feb 25, 2025·v1.2.0

Security Headers

improved

Security Headers Out of the Box

Added security headers to next.config.ts applied to all routes:

  • X-Frame-Options: DENY - Prevents clickjacking by blocking iframe embedding
  • X-Content-Type-Options: nosniff - Stops browsers from MIME-type sniffing
  • Referrer-Policy: strict-origin-when-cross-origin - Controls referrer information leakage
  • X-DNS-Prefetch-Control: on - Enables DNS prefetching for external links
  • Strict-Transport-Security - Forces HTTPS with 2-year max-age, subdomains, and preload
Feb 24, 2025·v1.1.0

SEO Structured Data & Canonical URLs

improved

Canonical URLs on Every Page

All 17 pages now include explicit alternates.canonical in their metadata. This prevents search engines from indexing duplicate URLs caused by trailing slashes, query parameters, or UTM tracking tags.

Product Structured Data (SoftwareApplication)

The landing page and pricing page now inject SoftwareApplication JSON-LD with AggregateOffer, showing your price range directly from your billing config. This can trigger rich results in Google displaying your pricing in search.

FAQ Rich Results

Added FAQPage JSON-LD schema on the landing page and pricing page. Google can now display your FAQ questions and answers directly in search results, expanding your listing's real estate. FAQ content is extracted to a shared data file so the UI and structured data stay in sync automatically.

Breadcrumb Schema for Blog

Blog posts and category pages now include BreadcrumbList JSON-LD. Google will display clean breadcrumb trails (Home > Blog > Post Title) instead of raw URLs in search results.

What Changed

  • Added alternates.canonical to all page metadata exports
  • Injected productJsonLd() on / and /pricing
  • Removed fabricated review from product schema (Google penalizes fake reviews)
  • Added faqJsonLd() on / and /pricing
  • Extracted FAQ data to components/landing/faq-data.ts for shared use
  • Added breadcrumbJsonLd() helper to lib/seo.ts
  • Injected breadcrumb schema on /blog/[slug] and /blog/category/[category]
Feb 20, 2025·v1.0.0

SecureStartKit Public Launch

new

SecureStartKit is Live

We're excited to launch SecureStartKit - a production-ready Next.js SaaS template built with security as the foundation.

What's Included

  • Security-first architecture - Backend-only data access, Zod validation, RLS-enabled database
  • Next.js 15 - App Router, Server Components, Server Actions
  • Supabase - Postgres database, authentication, type-safe queries
  • Stripe - Subscriptions and one-time payments with webhook handling
  • React Email + Resend - Beautiful transactional emails
  • Landing page - 7 customizable sections (Hero, Features, Testimonials, Pricing, FAQ, CTA, Footer)
  • Blog & Docs - MDX-powered content with categories, RSS, and sidebar navigation
  • Admin panel - User management and purchase tracking
  • Dark mode - System-aware theme with toggle
  • SEO - Sitemap, Open Graph images, structured data, RSS feed
  • i18n ready - Optional internationalization support

Pricing

  • Starter - $199 (one-time)
  • Pro - $299 (one-time, includes admin panel, email templates, i18n, priority support)

Both tiers include lifetime updates and full source code access.

Jan 15, 2024·v1.0.0

Initial Release

new

New Features

  • Authentication - Email/password and Google OAuth
  • Stripe Integration - Subscriptions and one-time payments
  • Dashboard - User overview, settings, and billing
  • Blog System - MDX-based blog with categories and RSS
  • Email Templates - Welcome, verification, and password reset
  • Admin Panel - User management and subscription overview
  • Landing Page - Hero, features, testimonials, pricing, FAQ sections
  • Dark Mode - Light and dark theme with system preference detection