Skip to content
ORSEN
All posts
Architecture2 min read

Seven architectural decisions for Lighthouse 100

A clean 100 in production isn't a lucky bug fix — it's the sum of decisions made up front.

Lighthouse 100 isn't earned by polishing code at the end. It's earned by making the same seven decisions up front. We re-take them on every project — and that's why launch night isn't a panic for the last three points.

1. Server Components by default

If a page doesn't need interactivity, don't ship client JS. Marketing, blog, docs, product category pages — server only. 60–80% of bundle goes away with this one decision.

export default async function ServicePage({ params }) {
  const { locale, slug } = await params;
  const service = await getService(locale, slug);
  return <ServiceTemplate service={service} />;
}

Rule of thumb

Pause two seconds before typing "use client". Is there real state — or just decoration?

2. Images: always next/image, always sized

Every img must arrive with explicit width and height. The single biggest LCP killer is CLS — and CLS comes mostly from sizeless images.

  • Hero: priority={true}, fetchPriority="high"
  • Below-fold: loading="lazy" is already default
  • AVIF + WebP fallbacks happen automatically

3. Fonts: subset + display swap

Latin-only subset, font-display: swap, self-hosted via next/font. A 200KB Inter file drops to 18KB when configured right.

import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"], display: "swap" });

For Turkish content, use subsets: ["latin", "latin-ext"] — ş, ğ, ı live in latin-ext.

4. CSS: one file, critical inlined

With Tailwind v4 the bundle is itself a critical path concern. Atomic CSS lands at 5–15KB per route — Next 15 inlines anything ≤14KB.

5. Animation: GPU layer, single rail

Aurora blobs, parallax, micro-interactions — all on transform + opacity. Animating top or left doesn't show in Lighthouse but kills real-user FPS.

.aurora-blob {
  transform: translate3d(0, 0, 0);
  will-change: transform;
  animation: aurora-shift 32s ease-in-out infinite;
}

Common mistake

backdrop-filter: blur(24px) on a sticky header halves scroll FPS. blur(12px) looks identical and ships.

6. Third-parties: always next/script strategy

Analytics, chat widgets, tag managers — all strategy="afterInteractive" or "lazyOnload". Nothing loads on the critical path. With Plausible you're at 1KB; with GTM, prefer direct GA4 + next/third-parties.

7. JSON-LD: build-time, not runtime

Generate schema markup in the server render — don't ship it to the client.

export default function Page() {
  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify(orgSchema),
        }}
      />
      <Hero />
    </>
  );
}

Seven decisions — and an eighth: measure. Run Lighthouse in CI on a mobile preset, set a performance budget. Production has a fixed target; a dip is a regression, not a polish task.

Related posts

LET'S BUILD

Make your next product the winning one.

A 30-minute discovery call lands you a clear scope, timeline and investment.