All postsTech

I Rated My Own Website 6/10 as a Hater. The Full Fix List, Commit by Commit.

Open Lighthouse on your own site right now. If SEO says 100, you probably closed the tab feeling good about yourself. I did that for months.

Garvit Sharma

Garvit Sharma

15 June 2026 · 6 MIN READ

Tech

I Rated My Own Website 6/10 as a Hater. The Full Fix List, Commit by Commit.

webight.com
On this page

Open Lighthouse on your own site right now. If SEO says 100, you probably closed the tab feeling good about yourself. I did that for months.

Then in June 2026 I audited webight.com the way I audit client sites before a pitch: hunting for reasons to call it garbage. Mobile scores said 90/96/96/100 (performance, accessibility, best practices, SEO). My hater rating said 6/10. Both were true at the same time, and the gap between those two numbers is everything this post is about.

See, the scores measure what a machine can check. The 6/10 came from what the machine can't: canonicals pointing at the wrong host, a sitemap full of dead pages, a hero headline that stayed invisible for 4,190ms, dev tooling scripts shipping to paying visitors, and a blog page with zero posts on a site that sells websites. All of it sitting in production while I told clients I obsess over detail.

Why I could not see my own bugs

There's a name for this in psychology: confirmation bias. You run the tests you expect to pass, you read the results that agree with you, and you stop. I had read Psycho-Cybernetics twice, so I knew the deeper layer too. Your self-image drives your behavior. My self-image was "the guy who ships fast, clean sites." So my brain filed webight.com under "fast and clean" and quietly stopped checking. The 100 on SEO became a permission slip to never open the page source again.

The trick that broke the spell was stupid and it worked. I opened a blank doc and wrote one line at the top: "This site is bad. Find the proof." Then I audited it like a competitor would before stealing my client. Different question, completely different findings.

Here's the full list, in the order I committed the fixes.

Fix 1: the canonical pointed at a host I redirect away from

Every page carried <link rel="canonical" href="https://webight.com/...">. Meanwhile the server 307-redirects webight.com to www.webight.com. So I was telling Google "the real version of this page lives at an address that bounces you somewhere else." For months. With a perfect SEO score, because Lighthouse only checks that a canonical exists and is parseable, never that it survives your own redirects.

The fix is one line in the root layout:

// src/app/layout.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://www.webight.com"),
  alternates: {
    canonical: "./",
  },
};

metadataBase makes every relative canonical and Open Graph URL resolve against the www host, and canonical: "./" lets each route generate its own self-referencing canonical. Pick one host, redirect to it, canonical to it. The three have to agree or you are splitting your own ranking signals between two addresses.

While I was in there, the sitemap. It still listed 12 case-study URLs I had deleted from the site weeks earlier. Twelve invitations for Googlebot to crawl a 404. The fix was moving from a stale static file to a generated one:

// src/app/sitemap.ts
import type { MetadataRoute } from "next";

export default function sitemap(): MetadataRoute.Sitemap {
  const base = "https://www.webight.com";
  const routes = ["", "/services", "/work", "/pricing", "/about", "/contact", "/blog"];
  return routes.map((path) => ({
    url: `${base}${path}`,
    lastModified: new Date(),
    changeFrequency: "monthly",
    priority: path === "" ? 1 : 0.7,
  }));
}

Now a deleted page falls out of the sitemap by definition, because the sitemap is built from the routes that actually exist.

Fix 2: a headline that took 4,190ms to show up

This was the ugly one. The LCP element was my hero headline, and the performance trace showed a 4,190ms element render delay. The HTML arrived fast. The text was in the document. But it was wrapped in a framer-motion fade, which means it sat at opacity 0 until the JavaScript bundle downloaded, parsed, and hydrated. On a mid-range phone over 4G, that's four full seconds of a visitor staring at the spot where my pitch should be.

A fade-in needs zero JavaScript. CSS animations start the moment the browser paints, no hydration required. Before:

// Before: invisible until hydration finishes
<motion.h1
  initial={{ opacity: 0, y: 24 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.6, ease: "easeOut" }}
>
  {headline}
</motion.h1>

After:

/* globals.css */
@keyframes hero-enter {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.hero-enter {
  animation: hero-enter 0.6s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .hero-enter {
    animation: none;
  }
}
// After: animates on first paint
<h1 className="hero-enter">{headline}</h1>

Same visual result. Render delay went from 4,190ms to 382ms. One commit, ten times faster, and the animation only touches translateY and opacity so it runs on the compositor instead of triggering layout. I still use framer-motion further down the page where it earns its weight. Above the fold, on the LCP element, it was costing me four seconds to do what CSS does for free.

Fix 3: two render-blocking stylesheets

Next, the trace showed two stylesheets blocking first paint, 22.5 KiB between them. Next.js has an experimental flag that inlines the CSS into the HTML so the browser never makes those blocking requests:

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    inlineCss: true,
  },
};

export default nextConfig;

One flag, both render-blocking requests gone. It is marked experimental, so test your build before trusting it, but on a Tailwind project where the shipped CSS is small anyway, inlining is close to a free win.

Fix 4: dev scripts riding along to production

This one embarrassed me the most. Two leftover third-party scripts from the original site tooling were still loading for every visitor. Scripts I had stopped needing months ago, downloaded by every phone that opened my homepage, on the website of a studio that charges money for performance work. Nobody flagged it because nobody reads their own network tab with hostile eyes. Deleted both. That's it. Some fixes are one minute of work and months of not looking.

Fix 5: security headers that should have been there from day one

Best practices was sitting at 96 partly because the site shipped with no security headers at all. Also fixed in next.config.ts:

// next.config.ts (add to the same config object)
async headers() {
  return [
    {
      source: "/(.*)",
      headers: [
        { key: "X-Content-Type-Options", value: "nosniff" },
        { key: "X-Frame-Options", value: "DENY" },
        { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
        {
          key: "Strict-Transport-Security",
          value: "max-age=63072000; includeSubDomains; preload",
        },
      ],
    },
  ];
},

Copy that block as is. It costs nothing at runtime and closes off clickjacking, MIME sniffing, and downgrade attacks in one commit.

Fix 6: the blog with zero posts

The audit doc also had a line no Lighthouse run will ever produce: "blog page exists, blog is empty." A studio that tells clients content builds trust, with a /blog route serving nothing. That fix is the post you are reading.

The scoreboard

Before: 90/96/96/100 on mobile. After: 96/100/100/100.

The whole list took two evenings. Two evenings of work that sat undone for months, because a green circle told me I was fine and I wanted to believe it.

And here's the part I keep circling back to, because it's the actual lesson. Every single one of these bugs was visible the entire time. The canonical mismatch was one curl command away. The render delay was in the first trace I never ran. The dev scripts were in the network tab. Pride is a filter on your own eyes. The 100 was accurate the whole time. I was using it to lie to myself.

If you want to run the same exercise today, the method is exactly three steps. Write "this site is bad, find proof" at the top of a doc. Run a mobile Lighthouse trace and read the LCP breakdown, the network tab, and the response headers like you're billing an enemy for the report. Fix the list in order of user pain, one commit each, and remeasure after every commit so you know which fix did what.

So before you screenshot your next 100 and feel safe, audit your site as the hater, because he finds in two evenings what the proud builder misses for months.

Found something ugly on your own site doing this? Tell me, I want to hear the worst one.

web performancenext.jsseolighthousetechnical audit
Next move

We build custom platforms, websites, and automation.

The two people who build it are the two you talk to, and every price is on the page.

Garvit Sharma

Garvit Sharma

Full-stack developer and co-founder of Webight, a two-person web and AI studio in India. He writes these from real client work. More about us.