<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: NOGUCHILin</title>
    <description>The latest articles on DEV Community by NOGUCHILin (@noguchilin).</description>
    <link>https://dev.to/noguchilin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008732%2Fdbccd4a5-b4ab-499f-b2cf-c38da232e5dc.png</url>
      <title>DEV Community: NOGUCHILin</title>
      <link>https://dev.to/noguchilin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noguchilin"/>
    <language>en</language>
    <item>
      <title>The on-page SEO problems you can't see are in the HTML — so I made Claude Code read it</title>
      <dc:creator>NOGUCHILin</dc:creator>
      <pubDate>Sat, 11 Jul 2026 02:12:40 +0000</pubDate>
      <link>https://dev.to/noguchilin/the-on-page-seo-problems-you-cant-see-are-in-the-html-so-i-made-claude-code-read-it-k0e</link>
      <guid>https://dev.to/noguchilin/the-on-page-seo-problems-you-cant-see-are-in-the-html-so-i-made-claude-code-read-it-k0e</guid>
      <description>&lt;p&gt;A couple of weeks ago I built a Claude Code skill that measures &lt;em&gt;why a page is slow&lt;/em&gt; (SpeedKit). While I was auditing all those "roast my site" pages, I kept noticing something: the SEO problems were as invisible as the speed ones — and just as mechanical to check.&lt;/p&gt;

&lt;p&gt;Not keyword strategy. Not backlinks. The boring, technical stuff that quietly keeps a page out of search or makes it look broken in the results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an accidental &lt;code&gt;noindex&lt;/code&gt; in the robots meta — the page is literally telling Google to drop it, and nobody notices&lt;/li&gt;
&lt;li&gt;a canonical pointing at a &lt;em&gt;different&lt;/em&gt; URL than the one you want ranked&lt;/li&gt;
&lt;li&gt;a 195-character meta description that gets truncated with an ellipsis in the SERP&lt;/li&gt;
&lt;li&gt;a title cut off at 60 chars, or zero/multiple H1s&lt;/li&gt;
&lt;li&gt;missing Open Graph tags, so the link renders bare and unclickable-looking when shared on Slack/X/LinkedIn&lt;/li&gt;
&lt;li&gt;JSON-LD structured data that's &lt;em&gt;present but malformed&lt;/em&gt;, so it does nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is readable straight from the page's HTML. You don't need a crawler subscription or an API key — you need to actually look. Which is exactly the kind of mechanical, every-time checklist Claude Code is good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I made it read the page
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;onpage-seo-audit&lt;/code&gt; is a SKILL.md plus a &lt;code&gt;seo-measure.js&lt;/code&gt; that runs in the page context and reads: the title and its length, the meta description length, H1 count and heading order, the canonical (and whether it points at the current URL), the robots meta, Open Graph + Twitter cards, JSON-LD blocks and their &lt;code&gt;@type&lt;/code&gt;, image alt coverage, &lt;code&gt;lang&lt;/code&gt;/viewport/charset, internal-vs-external links, body word count, and it fetches &lt;code&gt;/robots.txt&lt;/code&gt; and &lt;code&gt;/sitemap.xml&lt;/code&gt; to check crawlability.&lt;/p&gt;

&lt;p&gt;It returns a ranked report — indexability blockers first — where every finding is a &lt;strong&gt;real value read from the page&lt;/strong&gt;, not a guess.&lt;/p&gt;

&lt;p&gt;Here's the shape of the canonical check, which is my favorite because it catches a genuinely sneaky bug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonicalHref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link[rel="canonical"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canonicalHref&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;canonicalHref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// self === false → the page says the "real" version is a *different* URL.&lt;/span&gt;
&lt;span class="c1"&gt;// Sometimes that's correct (www → non-www consolidation). Sometimes you've&lt;/span&gt;
&lt;span class="c1"&gt;// just told Google to de-index the page you're looking at. Worth a human look.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part that mattered most: keeping it honest
&lt;/h2&gt;

&lt;p&gt;The whole thing only works if the model reports what it measured and nothing else. The SKILL.md is explicit about it: report the real character count, the real flag, the real ratio — never invent a metric you didn't read. And when a "problem" is plausibly intentional (a canonical consolidating www → non-www, a deliberately short landing page), say &lt;em&gt;"verify this"&lt;/em&gt; rather than &lt;em&gt;"this is broken."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It also refuses to promise rankings. On-page SEO removes the technical reasons a page &lt;em&gt;can't&lt;/em&gt; be indexed or &lt;em&gt;looks wrong&lt;/em&gt; in search. That's the honest scope — no keyword-density myths, no "submit to 500 directories."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the pattern on your own site
&lt;/h2&gt;

&lt;p&gt;Open DevTools → Console on any page and check the list at the top by hand — you'll probably find at least one. That's most of the value right there.&lt;/p&gt;

&lt;p&gt;If you'd rather not remember to, I packaged it (audit + a fix skill that applies the corrections and re-reads the page) as &lt;strong&gt;SEO Audit for Claude Code&lt;/strong&gt; ($19): &lt;a href="https://noguchilin.gumroad.com/l/gywwrj" rel="noopener noreferrer"&gt;https://noguchilin.gumroad.com/l/gywwrj&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's the sibling of the speed one — &lt;a href="https://noguchilin.gumroad.com/l/ywtkug" rel="noopener noreferrer"&gt;SpeedKit&lt;/a&gt; reads &lt;em&gt;why a page is slow&lt;/em&gt;, this one reads &lt;em&gt;why it can't be found.&lt;/em&gt; Two halves of the same "is this site technically healthy" question.&lt;/p&gt;

&lt;p&gt;Happy to run the audit on your page if you drop a URL in the comments.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>claude</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I turned 38 "why is my site slow" audits into a Claude Code skill</title>
      <dc:creator>NOGUCHILin</dc:creator>
      <pubDate>Sat, 11 Jul 2026 00:32:59 +0000</pubDate>
      <link>https://dev.to/noguchilin/i-turned-38-why-is-my-site-slow-audits-into-a-claude-code-skill-2jni</link>
      <guid>https://dev.to/noguchilin/i-turned-38-why-is-my-site-slow-audits-into-a-claude-code-skill-2jni</guid>
      <description>&lt;p&gt;For the last couple of weeks I've been replying to "roast my website" and "why is my site slow" posts — the ones where someone drops a URL and asks for brutal feedback. Not with opinions. I'd actually measure the page and reply with numbers.&lt;/p&gt;

&lt;p&gt;After ~38 of these, the same handful of problems kept showing up. Not the ones people expect. The boring, invisible ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Images served 14x larger than they display.&lt;/strong&gt; A logo shipped at 768x768, rendered at 56x56. A hero exported straight from Figma at full res. This was the single most common win, on more than half the sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same file downloaded twice.&lt;/strong&gt; Two different &lt;code&gt;?dpl=&lt;/code&gt; deploy-id query strings on the &lt;em&gt;same&lt;/em&gt; font file — different cache entries, so the browser fetches it twice and the preload never matches. ~15% of the page, wasted, silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The favicon eating the page.&lt;/strong&gt; One site's tab icon was 914KB — 95% of total page weight. A &lt;code&gt;.ico&lt;/code&gt; that was really a full-size logo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbundled ES-module waterfalls.&lt;/strong&gt; 74 separate JS files resolving imports one after another. On the dev's own machine (near-zero latency) it felt fine; for real visitors every level of the import chain added a round trip. 8+ seconds to interactive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High TTFB with no CDN.&lt;/strong&gt; The server thinking for 1.5s before a single byte, on a page whose images were already fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these show up if you eyeball a site. You have to measure. So I'd open DevTools, run the same Performance-API checks every time, rank the findings by impact, and write them up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I got tired of doing it by hand
&lt;/h2&gt;

&lt;p&gt;The measurement was mechanical — the same script every time — and the write-up followed the same rubric. That's exactly the kind of thing Claude Code is good at. So I packaged the whole workflow into two skills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web-speed-audit&lt;/code&gt; — point Claude at a URL, it runs the measurement pass and returns a ranked report where every finding is backed by a real number.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web-speed-fix&lt;/code&gt; — "now fix the top 3" and it applies the fixes in impact order (right-size images, bundle JS, add a CDN, de-dupe downloads), then &lt;strong&gt;re-measures&lt;/strong&gt; so you see before/after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The detectors are just the patterns above, encoded. Here's the shape of the oversized-image check (the full engine also handles timing, weight, duplicates, waterfalls, favicons, TTFB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oversized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentSrc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linear&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// served 2x+ larger than displayed&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linear&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linear&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Does it actually work?
&lt;/h2&gt;

&lt;p&gt;The playbook is the same one I used to rebuild a slow landing page and take Lighthouse mobile from &lt;strong&gt;44 to 99&lt;/strong&gt; (LCP 13.9s to 2.0s, TBT 1920ms to 0, ~1/15 the transfer weight). Both before/after versions are public so you can run them through PageSpeed yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want the packaged version
&lt;/h2&gt;

&lt;p&gt;I put the two skills + a pre-optimized Astro starter + the 44-to-99 case study together as &lt;strong&gt;SpeedKit for Claude Code&lt;/strong&gt; ($29): &lt;a href="https://noguchilin.gumroad.com/l/ywtkug" rel="noopener noreferrer"&gt;https://noguchilin.gumroad.com/l/ywtkug&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But honestly, if you just take the pattern list above and check your own site against it in DevTools, you'll catch most of what I catch. The value of the kit is not having to remember to.&lt;/p&gt;

&lt;p&gt;Happy to run a quick audit on your site if you drop the URL in the comments.&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>claude</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>3 patterns I keep finding when I actually measure "slow" landing pages</title>
      <dc:creator>NOGUCHILin</dc:creator>
      <pubDate>Fri, 10 Jul 2026 02:01:26 +0000</pubDate>
      <link>https://dev.to/noguchilin/3-patterns-i-keep-finding-when-i-actually-measure-slow-landing-pages-59g7</link>
      <guid>https://dev.to/noguchilin/3-patterns-i-keep-finding-when-i-actually-measure-slow-landing-pages-59g7</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I spend a chunk of most weeks replying to "roast my website" posts with real numbers instead of opinions. This week's batch turned up the same three problems, over and over, on completely unrelated sites (a SaaS API, a kids' chore app, a local service business). If you only fix one thing on your landing page this month, it's probably one of these three.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The one oversized image that's secretly most of your page
&lt;/h2&gt;

&lt;p&gt;Pattern: a single image — usually a logo, hero photo, or video poster — accounts for 40-90% of total page weight, because it was uploaded at its original resolution instead of resized for where it's actually displayed.&lt;/p&gt;

&lt;p&gt;Two real examples from this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A logo displayed at 167x72px in the header, shipped at its original 2726x1177px — about 16x more pixels than anyone would ever see. It alone was 143KB of a 195KB page (73%).&lt;/li&gt;
&lt;li&gt;A profile photo displayed at 56x56px, shipped at 1080x1080px. 911KB of a 2.1MB page (43%) was that one photo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is boring and doesn't require touching your framework: export images at roughly 2-3x their actual display size, not their original camera/design resolution. If you're not sure what's oversized, open DevTools -&amp;gt; Network, sort by size, and look at whichever request is way bigger than you'd expect for what it visually is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// quick check in the console: rendered size vs actual file size&lt;/span&gt;
&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rendered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;natural&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalWidth&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;naturalHeight&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;natural&lt;/code&gt; is more than ~2x &lt;code&gt;rendered&lt;/code&gt; in either dimension, that image is a resize-before-upload candidate.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Lossless formats for photographic content
&lt;/h2&gt;

&lt;p&gt;Related but distinct from #1: a correctly-sized image can still be bloated because it's saved as PNG when it's a photo or screenshot.&lt;/p&gt;

&lt;p&gt;One site had a video poster frame — a UI screenshot, basically a photo — saved as a 1920x1080 PNG at 293KB (47% of the page's total weight). PNG is lossless, which is right for logos/icons/flat graphics with sharp edges, but wasteful for anything photographic. Re-exporting the same image as WebP or an optimized JPEG typically gets you 80-90% smaller with no visible difference, because you're finally using a format designed for what the image actually is.&lt;/p&gt;

&lt;p&gt;Rule of thumb: flat graphics (logos, icons, illustrations with few colors) -&amp;gt; PNG or SVG. Photos, screenshots, anything with gradients/noise -&amp;gt; JPEG or WebP.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. JS weight that doesn't match what the page does
&lt;/h2&gt;

&lt;p&gt;The last pattern is less visual and more structural: a landing page that's mostly static content (headline, a few sections, a CTA) shipping 200-300KB of JavaScript across a dozen-plus chunks, because the framework's default code-splitting wasn't reconsidered for a page that doesn't need much client-side interactivity.&lt;/p&gt;

&lt;p&gt;One example: 66% of a 379KB landing page was JS, split across 12+ separate chunks, for a page whose actual job is "explain what this API does in five seconds." None of that JS was doing anything wrong exactly — it's just what you get by default from a JS framework's build if nobody goes back and asks "does this page need to be this dynamic?"&lt;/p&gt;

&lt;p&gt;Same audit also turned up a broken analytics call (503 then 404 on the tracking init) — worth checking your Network tab for red rows even when the page feels fine, because a broken analytics pixel fails silently and you just... don't get the funnel data you think you're getting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters more than it sounds
&lt;/h2&gt;

&lt;p&gt;None of these are exotic. They're the kind of thing that gets waved off as "we'll optimize later" — except later rarely comes, and in the meantime every visitor on a slow connection is paying for it in bounce rate before they've read a single word of your copy. The fix for all three is usually a few hours, not a rebuild.&lt;/p&gt;

&lt;p&gt;If you want a free pass on your own site — no pitch, just the actual numbers — I still do this on Reddit/X most days (search "roast my landing page"). If it turns out the problem is bigger than a few images (routing, architecture, a full rebuild), that's the kind of thing I take on as paid work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astro Rebuild Pack (Contra): &lt;a href="https://contra.com/s/kslvEs2R-astro-rebuild-pack-turn-your-slow-site-into-a-fast-one" rel="noopener noreferrer"&gt;https://contra.com/s/kslvEs2R-astro-rebuild-pack-turn-your-slow-site-into-a-fast-one&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I rebuilt a slow landing page in Astro and took Lighthouse from 44 to 99</title>
      <dc:creator>NOGUCHILin</dc:creator>
      <pubDate>Mon, 29 Jun 2026 20:49:34 +0000</pubDate>
      <link>https://dev.to/noguchilin/i-rebuilt-a-slow-landing-page-in-astro-and-took-lighthouse-from-44-to-99-199i</link>
      <guid>https://dev.to/noguchilin/i-rebuilt-a-slow-landing-page-in-astro-and-took-lighthouse-from-44-to-99-199i</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rebuilding a heavy landing page in &lt;strong&gt;Astro&lt;/strong&gt; moved the Lighthouse performance score from &lt;strong&gt;44 → 99&lt;/strong&gt; (measured).&lt;/li&gt;
&lt;li&gt;Most of the weight was &lt;strong&gt;JavaScript&lt;/strong&gt;, &lt;strong&gt;images&lt;/strong&gt;, and &lt;strong&gt;render-blocking resources&lt;/strong&gt;. Astro's "zero JS by default" does the heavy lifting.&lt;/li&gt;
&lt;li&gt;I ran the work &lt;strong&gt;async through Claude Code&lt;/strong&gt;. The human only owns direction and verification.&lt;/li&gt;
&lt;li&gt;There's a live before/after demo you can poke at (below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "make it fast" write-up. The flashy part (text exploding into particles and reassembling — &lt;code&gt;glyphdust&lt;/code&gt;) gets one paragraph at the end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Astro (what "slow" actually is)
&lt;/h2&gt;

&lt;p&gt;When a landing page is slow, it's usually one of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Too much JavaScript&lt;/strong&gt; — decoration libraries, analytics tags, unused bundles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unoptimized images&lt;/strong&gt; — full-size PNGs, no &lt;code&gt;width/height&lt;/code&gt; (hello CLS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render-blocking&lt;/strong&gt; — CSS/JS loaded synchronously in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Astro attacks this structurally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Island architecture&lt;/strong&gt;: zero client JS by default; only the components you mark with &lt;code&gt;client:*&lt;/code&gt; get hydrated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image optimization&lt;/strong&gt;: &lt;code&gt;astro:assets&lt;/code&gt; &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;Picture /&amp;gt;&lt;/code&gt; handles &lt;code&gt;width/height&lt;/code&gt;, lazy loading, and modern formats (the demo uses &lt;code&gt;&amp;lt;Picture&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-time rendering&lt;/strong&gt;: HTML is static; CSS is easy to scope and inline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point isn't "switch frameworks." It's "&lt;strong&gt;structurally ship less JS&lt;/strong&gt;."&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually did (checklist)
&lt;/h2&gt;

&lt;p&gt;I used a fictional cafe ("KURADO") landing page, built &lt;strong&gt;twice with the same visual design&lt;/strong&gt;: a Before (heavy) and an After (Astro).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Replace images with astro:assets &amp;lt;Picture /&amp;gt; (width/height set, modern formats auto)
[ ] Hero image loading="eager", everything else loading="lazy"
[ ] Self-host fonts; preload variable fonts with &amp;lt;link rel="preload" as="font" crossorigin&amp;gt;
[ ] Remove decorative JS, or island-ify it (client:visible, only when needed)
[ ] Scope CSS per component; drop the unused parts
[ ] async/defer third-party scripts; lazy-load where possible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: for self-hosted fonts use &lt;code&gt;preload&lt;/code&gt;, not &lt;code&gt;preconnect&lt;/code&gt; (&lt;code&gt;preconnect&lt;/code&gt; is for warming up connections to external origins). The After also preloads two latin-subset variable fonts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing exotic. &lt;strong&gt;Just killing JS, images, and blocking&lt;/strong&gt; moves the numbers a lot.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results (measured)
&lt;/h2&gt;

&lt;p&gt;Same look, built two ways, Lighthouse on both (my own demo, fictional cafe):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (heavy)&lt;/th&gt;
&lt;th&gt;After (Astro)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance score&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCP&lt;/td&gt;
&lt;td&gt;13.9s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.0s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TBT&lt;/td&gt;
&lt;td&gt;1920ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer size&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1/15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS&lt;/td&gt;
&lt;td&gt;~0&lt;/td&gt;
&lt;td&gt;~0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Honest footnote: CLS was already ~0 in the Before (it wasn't a layout problem). &lt;strong&gt;Not inflating numbers&lt;/strong&gt; matters, so I only claim the metrics that actually improved (LCP / TBT / transfer size).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LCP 13.9s → 2.0s, TBT 1920ms → 0ms. That's where the felt difference is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running it async with Claude Code
&lt;/h2&gt;

&lt;p&gt;A human doesn't need to do all of this by hand. I hand it to Claude Code and keep &lt;strong&gt;direction and verification&lt;/strong&gt; for myself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Replace this image set with &lt;code&gt;astro:assets&lt;/code&gt; &lt;code&gt;&amp;lt;Picture /&amp;gt;&lt;/code&gt;, set &lt;code&gt;width/height&lt;/code&gt; from intrinsic dimensions."&lt;/li&gt;
&lt;li&gt;"Extract the XX decoration into a &lt;code&gt;client:visible&lt;/code&gt; island."&lt;/li&gt;
&lt;li&gt;"Run Lighthouse before/after and report the diff."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key is &lt;strong&gt;verifying with an independent command&lt;/strong&gt; — not "it should be faster," but actually running Lighthouse and reading the numbers. As long as the human holds that, the implementation can be safely delegated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Signature: glyphdust
&lt;/h2&gt;

&lt;p&gt;It's a speed post, but I leave one "signature" of my work on it: &lt;strong&gt;glyphdust&lt;/strong&gt;, a small OSS react-three-fiber component I built.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: in the demo above, glyphdust is &lt;strong&gt;only named as a signature&lt;/strong&gt; — the hero itself is a performance-first static implementation (piling on effects in a speed article would defeat the point). Here's what glyphdust does on its own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a single scroll progress &lt;code&gt;0 → 1&lt;/code&gt;, it shatters text into thousands of GPU particles → recomposes them into another glyph → and finally resolves to &lt;strong&gt;real, selectable DOM text&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GlyphDust&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;glyphdust&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GlyphDust&lt;/span&gt;
  &lt;span class="na"&gt;keyframes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;KURADO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;domSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#headline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scatter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;spread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dense&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resolveToDom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;three&lt;/code&gt; / &lt;code&gt;@react-three/fiber&lt;/code&gt; / &lt;code&gt;react&lt;/code&gt; are peer dependencies (React 18+, three 0.160+). With &lt;code&gt;prefers-reduced-motion&lt;/code&gt; or no WebGL, it falls back to your own static markup (&lt;strong&gt;the screen never goes blank&lt;/strong&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/linno-inc/glyphdust" rel="noopener noreferrer"&gt;https://github.com/linno-inc/glyphdust&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last spoonful for pages that are both fast &lt;em&gt;and&lt;/em&gt; memorable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo, and if you want a rebuild
&lt;/h2&gt;

&lt;p&gt;There's a live before/after demo you can touch (my own sample, fictional cafe):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo (/ , /after , /before)&lt;/strong&gt;: &lt;a href="https://astro-rebuild-demo.pages.dev/" rel="noopener noreferrer"&gt;https://astro-rebuild-demo.pages.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've got a slow landing page or small site and want it fast &lt;strong&gt;without changing how it looks&lt;/strong&gt;, I offer a fixed-scope, fully async (no calls) package on Contra:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Astro Rebuild Pack (Contra)&lt;/strong&gt;: &lt;a href="https://contra.com/s/kslvEs2R-astro-rebuild-pack-turn-your-slow-site-into-a-fast-one" rel="noopener noreferrer"&gt;https://contra.com/s/kslvEs2R-astro-rebuild-pack-turn-your-slow-site-into-a-fast-one&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core Web Vitals pass guaranteed (if it doesn't pass, I keep working until it does). Delivery is a Loom walkthrough + a staging link. No meetings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Astro "structurally reduces JS," so a slow LP's score can move a lot (44 → 99, measured).&lt;/li&gt;
&lt;li&gt;The work is unglamorous (kill JS, images, blocking). You can run it async with Claude Code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always verify numbers by measuring. Don't inflate.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A spoonful of signature (glyphdust) for the finish.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>astro</category>
      <category>webperf</category>
      <category>corewebvitals</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
