The why behind astro-ignite

A warm gradient hero illustration with the title "Why astro-ignite".

Most Astro starters ship in one of two extremes: a minimal “hello world” you assemble yourself, or an opinionated framework that hides decisions behind abstractions. astro-ignite picks a different point on the spectrum — opinionated defaults you fully own.

Lighthouse 100s require defaults, not effort

Tailwind’s CSS bundle is a single render-blocking file containing every utility your site uses. For a hero rendering above the fold, that’s a real cost — even with Astro’s per-route splitting, cross-page utility overlap weighs your homepage CSS down with rules from /blog and /about.

The fix isn’t to drop Tailwind. The fix is to not use Tailwind for the hero: above-the-fold components (Hero, Nav, BaseLayout chrome) ship with scoped Astro <style> blocks that get inlined per-route automatically. The rest of the page uses Tailwind. A build-time critical CSS step (Beasties) inlines the Tailwind rules each page actually needs above the fold; the rest loads async.

This is the convention encoded in the scaffold by example. You don’t have to think about it. You just see Hero use a <style> block while Footer uses Tailwind classes, and you copy the pattern.

JSON-LD must be typed

Schema.org has hundreds of types and thousands of properties, most of which are optional or constrained. Hand-writing JSON-LD as plain objects guarantees you ship malformed structured data — Google Search Console will tell you, but only after the SERPs already saw it.

The fix is schema-dts, Google’s official TypeScript types for Schema.org. astro-ignite ships pure functions that build typed schema objects (BlogPosting, Organization, BreadcrumbList, etc.) and renders them into a single @graph script. Cross-references between schemas use @id, so BlogPosting.publisher is just { '@id': '...#organization' } — the data is normalized, the markup is small, and TypeScript yells if you ever try to put headline on a Person.

Why no React in the template

The interactive components a marketing/blog/portfolio site genuinely needs are tiny: a mobile nav toggle, a theme switcher, a locale dropdown, a cookie banner. None of them require a framework runtime. Each is 10–30 lines of vanilla JS in a <script> block, scoped to its component.

Shipping @astrojs/react by default would burn ~40KB of hydration on something that doesn’t need it. Users who want React can run npx astro add react — it’s one command. The default should defend the perf pitch, not undermine it.

Why two email providers

Email transports diverge based on infrastructure. Indie devs want Resend. Teams using their own SMTP (M365, Postmark, AWS SES) want SMTP. Forcing one is wrong. So the init prompt asks, and only the chosen provider lands in package.json. The action layer above (Astro Action + Zod validation + honeypot) is identical either way.

What’s intentionally not here

  • No icon library — small inline SVGs ship; users add astro-icon if they want more.
  • No class-variance-authority — the surface is too small to need it.
  • No CMS adapter — content lives in MDX collections, the canonical Astro pattern.
  • No analytics by default — Plausible is wired up but env-gated; nothing fires until you opt in.

The point isn’t to ship every feature. It’s to ship the right feature for an SEO/perf-grade Astro site, and to ship it with code you can read in an afternoon.