<?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: xg704863664</title>
    <description>The latest articles on DEV Community by xg704863664 (@xg704863664).</description>
    <link>https://dev.to/xg704863664</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%2F4161289%2Ff45f4cc1-24bd-4550-88ed-3e089c56a0ca.png</url>
      <title>DEV Community: xg704863664</title>
      <link>https://dev.to/xg704863664</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xg704863664"/>
    <language>en</language>
    <item>
      <title>What 49 pages of niche content taught me about writing for search</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:52:41 +0000</pubDate>
      <link>https://dev.to/xg704863664/what-49-pages-of-niche-content-taught-me-about-writing-for-search-5be8</link>
      <guid>https://dev.to/xg704863664/what-49-pages-of-niche-content-taught-me-about-writing-for-search-5be8</guid>
      <description>&lt;p&gt;I have written 49 pages of niche reference content in the last two weeks. Almost everything I assumed about writing for search was wrong in some small way. These are the corrections that actually changed the pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first sentence is the whole page
&lt;/h2&gt;

&lt;p&gt;The single highest-leverage edit I make to any draft is rewriting the sentence after the H1.&lt;/p&gt;

&lt;p&gt;Draft: &lt;em&gt;"Herbal remedies have been used for centuries to treat a wide range of ailments..."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Published: &lt;em&gt;"Most sore throats are viral and settle in 3–7 days. These remedies reduce the pain while that happens."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second one does three things the first does not: it gives a number, it sets an expectation, and it tells the reader whether this page is for them. That is &lt;a href="https://xg704863664.github.io/natural-remedies-for-sore-throat.html" rel="noopener noreferrer"&gt;the sore throat page&lt;/a&gt;, and the same rewrite pattern appears on every page I have published since.&lt;/p&gt;

&lt;p&gt;The test I use: &lt;strong&gt;read only the H1 and the first sentence.&lt;/strong&gt; If that pair does not answer the query, the page is not finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counterintuitive beats comprehensive
&lt;/h2&gt;

&lt;p&gt;The pages that get read are the ones that contradict a common belief. Not contrarian for its own sake — genuinely correcting something people are doing wrong.&lt;/p&gt;

&lt;p&gt;Examples from my own site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peppermint makes reflux worse.&lt;/strong&gt; It relaxes the valve at the top of the stomach. It is the most recommended herbal tea for digestion and the wrong one for heartburn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not heat a chicken coop in winter.&lt;/strong&gt; Sealing it traps moisture, which causes frostbite at temperatures well above freezing. Ventilation matters more than warmth — the &lt;a href="https://xg704863664.github.io/how-to-keep-chickens-warm-in-winter.html" rel="noopener noreferrer"&gt;full reasoning is here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spraying the ants you can see is the wrong move.&lt;/strong&gt; They are 1–10% of the colony; killing them makes the colony produce more, and you lose the trail that would lead you to the nest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those could have been written as neutral advice. The reflux one is on the &lt;a href="https://xg704863664.github.io/herbal-remedies-for-acid-reflux.html" rel="noopener noreferrer"&gt;acid reflux page&lt;/a&gt;, which is also the page where I had to admit that the most-recommended herb for a problem is the wrong one. Written as a correction, they are memorable and they are what people link to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer is a number, or it is not an answer
&lt;/h2&gt;

&lt;p&gt;Vague advice is unquotable. "Provide adequate space" is not useful. "2–3 sq ft per bird inside, 8–10 sq ft in the run" is useful, checkable and linkable.&lt;/p&gt;

&lt;p&gt;I now go through every draft and hunt for the sentence that &lt;em&gt;should&lt;/em&gt; contain a number but does not. There is almost always one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats belong on the page, not in a disclaimer
&lt;/h2&gt;

&lt;p&gt;The thing that separates a useful page from a sales page is that the useful page tells you when &lt;strong&gt;not&lt;/strong&gt; to use the thing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Licorice for reflux: use DGL, and not at all if you have high blood pressure.&lt;/li&gt;
&lt;li&gt;Elderberry syrup: never for children under one, because of the honey.&lt;/li&gt;
&lt;li&gt;Rosemary: almost every dead plant was overwatered, not frozen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This costs you nothing and it is the main reason people trust a page enough to link to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Length follows from the structure, not a target
&lt;/h2&gt;

&lt;p&gt;I stopped aiming for word counts. What I aim for now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A direct answer in the first sentence&lt;/li&gt;
&lt;li&gt;A table for the numbers&lt;/li&gt;
&lt;li&gt;The reasoning behind the numbers&lt;/li&gt;
&lt;li&gt;The exceptions and caveats&lt;/li&gt;
&lt;li&gt;FAQ entries that match what people actually search&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That lands between 450 and 700 words for most topics. The pages that run longer do so because the topic has more exceptions, not because longer is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal links are not decoration
&lt;/h2&gt;

&lt;p&gt;Every page links to two or three related pages, in context, with anchor text that describes the destination. Not "click here" and not a bare list at the bottom.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://xg704863664.github.io/how-deep-should-a-raised-bed-be.html" rel="noopener noreferrer"&gt;raised bed depth page&lt;/a&gt; links to the soil calculator because the next question after "how deep" is "how much soil". That is the whole principle: &lt;strong&gt;link to the next question.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting today
&lt;/h2&gt;

&lt;p&gt;Write the hub page first. I wrote 21 pages before creating the page that ties them together, which meant the crawl path between them did not exist for weeks. Everything else — the numbers, the caveats, the first sentence — is refinement. The structure is the thing that decides whether any of it is found.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>seo</category>
      <category>webdev</category>
      <category>content</category>
    </item>
    <item>
      <title>Most of my content pages are secretly tables. Here is the data shape that works</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:46:09 +0000</pubDate>
      <link>https://dev.to/xg704863664/most-of-my-content-pages-are-secretly-tables-here-is-the-data-shape-that-works-1d5p</link>
      <guid>https://dev.to/xg704863664/most-of-my-content-pages-are-secretly-tables-here-is-the-data-shape-that-works-1d5p</guid>
      <description>&lt;p&gt;Every content page I write starts as prose and ends up mostly tables. That was not the plan — it is what happened after watching which parts people actually used.&lt;/p&gt;

&lt;p&gt;Here is the data shape that kept working, and the three ways tables fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tables win for reference content
&lt;/h2&gt;

&lt;p&gt;A reader arriving from a search query has one question. If the answer is "2 to 3 square feet per bird", a paragraph makes them read three sentences to extract it. A table gives it to them in one glance.&lt;/p&gt;

&lt;p&gt;Compare the two forms for the same information:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hens need roughly two to three square feet of floor space inside the coop, and eight to ten square feet in the run. Crowding is the single biggest cause of pecking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;versus:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Space&lt;/th&gt;
&lt;th&gt;Per bird&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inside the coop&lt;/td&gt;
&lt;td&gt;2–3 sq ft&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In the run&lt;/td&gt;
&lt;td&gt;8–10 sq ft&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same content. The table is scannable, quotable, and — this matters — it survives being read on a phone at 7am by someone standing in a garden centre.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape that keeps working: two columns, one axis
&lt;/h2&gt;

&lt;p&gt;The tables that perform best have a &lt;strong&gt;single axis of variation&lt;/strong&gt;. Depth of a raised bed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Depth&lt;/th&gt;
&lt;th&gt;What grows well&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;15 cm&lt;/td&gt;
&lt;td&gt;Lettuce, herbs, radish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 cm&lt;/td&gt;
&lt;td&gt;Carrots, peppers, tomatoes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is the &lt;a href="https://xg704863664.github.io/how-deep-should-a-raised-bed-be.html" rel="noopener noreferrer"&gt;full depth chart&lt;/a&gt;, which also covers what to do if your bed is already built at the wrong depth.&lt;/p&gt;

&lt;p&gt;One column is the thing the reader controls; the other is the consequence. Three columns work if the third is genuinely supplementary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Food&lt;/th&gt;
&lt;th&gt;Pantry&lt;/th&gt;
&lt;th&gt;Fridge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;White rice&lt;/td&gt;
&lt;td&gt;2 years&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brown rice&lt;/td&gt;
&lt;td&gt;3–6 months&lt;/td&gt;
&lt;td&gt;6–12 months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://xg704863664.github.io/food-storage-time-lookup.html" rel="noopener noreferrer"&gt;full version of that one&lt;/a&gt; has five columns and a search box, because storage times have a genuine third dimension (sealed vs not).&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 1: tables that are really paragraphs
&lt;/h2&gt;

&lt;p&gt;If a cell contains a sentence with "however" in it, it is not tabular data. It is a paragraph wearing a table costume, and it reads badly.&lt;/p&gt;

&lt;p&gt;The test: &lt;strong&gt;can you read the table by scanning only the first column?&lt;/strong&gt; If not, the axis is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 2: tables that break on mobile
&lt;/h2&gt;

&lt;p&gt;A 700px table on a 390px screen forces the whole page to scroll horizontally. The fix is one wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table-scroll"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;table&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.table-scroll&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;-webkit-overflow-scrolling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;touch&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;Do not shrink the font instead. Scrolling one element is fine; scrolling the page is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 3: no caption or context
&lt;/h2&gt;

&lt;p&gt;A bare table assumes the reader already knows what the columns mean. One sentence above it removes that assumption:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Storage times assume an airtight container in a cool, dark place. Heat roughly halves every number here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is doing more work than any row in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The markup that matters
&lt;/h2&gt;

&lt;p&gt;Use real &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt;. It costs nothing and it gives screen readers the column association, so a cell is announced as "8–10 sq ft, in the run" rather than just "8–10 sq ft".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Space&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Per bird&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;Inside the coop&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;2–3 sq ft&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not do this on my first twenty pages and had to go back through all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where prose still wins
&lt;/h2&gt;

&lt;p&gt;Tables are for &lt;strong&gt;lookup&lt;/strong&gt;. Prose is for &lt;strong&gt;reasoning&lt;/strong&gt;. The most useful pages I have end up as both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A table for the numbers&lt;/li&gt;
&lt;li&gt;A short paragraph for why the numbers are what they are&lt;/li&gt;
&lt;li&gt;A section for the exceptions, which are almost never tabular&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://xg704863664.github.io/companion-planting-chart.html" rel="noopener noreferrer"&gt;companion planting chart&lt;/a&gt; is the clearest example. The chart is a table. But the useful part is the paragraph after it explaining that most of the pairings are folklore and only the structural effects (shade, support, trap cropping) are reliable. That paragraph could not be a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick test before publishing
&lt;/h2&gt;

&lt;p&gt;Read only the H2s, then only the first column of every table, then only the first sentence of every paragraph. If those three passes together do not answer the page's title question, the structure is wrong — regardless of how good the writing is.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>a11y</category>
      <category>css</category>
    </item>
    <item>
      <title>A static site SEO checklist I ran on my own site (and what it found)</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:43:01 +0000</pubDate>
      <link>https://dev.to/xg704863664/a-static-site-seo-checklist-i-ran-on-my-own-site-and-what-it-found-fbj</link>
      <guid>https://dev.to/xg704863664/a-static-site-seo-checklist-i-ran-on-my-own-site-and-what-it-found-fbj</guid>
      <description>&lt;p&gt;I moved a 21-page static site from a subdirectory to a domain root last week, and it broke more things than I expected. Here is the checklist I ended up running, in the order that found actual problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Does the crawler see the same page a human does?
&lt;/h2&gt;

&lt;p&gt;Fetch your page with a crawler user agent and compare the rendered text to what you see in a browser:&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;UA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&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;/&amp;lt;script&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;script&amp;gt;/g&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="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;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &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;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;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the crawler text is much shorter than what you see, something is client-rendered and you have a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also check &lt;code&gt;x-robots-tag&lt;/code&gt;.&lt;/strong&gt; A header-level noindex will silently kill a page even when the HTML has nothing wrong with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Does every page render without JavaScript?
&lt;/h2&gt;

&lt;p&gt;Disable JS and reload. For a content site, everything that matters — headings, paragraphs, tables — should still be there.&lt;/p&gt;

&lt;p&gt;I write calculators as progressive enhancement for this reason. The page contains a real table of numbers; the JS only adds the interactive layer on top. The &lt;a href="https://xg704863664.github.io/food-storage-time-lookup.html" rel="noopener noreferrer"&gt;food storage lookup&lt;/a&gt; is a good example: without JS it is a readable 24-row table, with JS it filters as you type.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Is the sitemap actually valid, or does it just look valid?
&lt;/h2&gt;

&lt;p&gt;Three things that caught me out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A BOM at the start of the file.&lt;/strong&gt; Check the first bytes — they should be &lt;code&gt;3c 3f 78&lt;/code&gt; (&lt;code&gt;&amp;lt;?x&lt;/code&gt;), not &lt;code&gt;ef bb bf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The wrong content type.&lt;/strong&gt; It should be &lt;code&gt;application/xml&lt;/code&gt;, not &lt;code&gt;text/plain&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The wrong URL.&lt;/strong&gt; If your property is a subdirectory and you submit &lt;code&gt;sitemap.xml&lt;/code&gt;, make sure you know what it resolves to. Fetch the resolved URL yourself rather than assuming.
&lt;/li&gt;
&lt;/ul&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;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// 3c3f78 = good&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. If you move URLs, do the old ones redirect?
&lt;/h2&gt;

&lt;p&gt;A meta refresh plus a canonical is the only option on a static host with no server config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/new-path"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"refresh"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"0; url=https://example.com/new-path"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"robots"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"noindex, follow"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;location&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/new-path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;noindex, follow&lt;/code&gt; combination is the part people miss. You want the old URL dropped from the index but its link equity passed on, and &lt;code&gt;noindex&lt;/code&gt; without &lt;code&gt;follow&lt;/code&gt; throws that away.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Is there a hub page linking to everything?
&lt;/h2&gt;

&lt;p&gt;This was my biggest structural mistake. I had 21 pages, all reachable from a nav bar, but no single page that linked to all of them with context. Google had indexed exactly one page — the homepage — and had not followed through to the rest.&lt;/p&gt;

&lt;p&gt;A hub with a one-line description per link fixed the structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/companion-planting-chart.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Companion Planting Chart&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"d"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;A practical table of what to plant together, and the pairings that are folklore.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the result on &lt;a href="https://xg704863664.github.io/" rel="noopener noreferrer"&gt;the hub page&lt;/a&gt; — 46 guides, each with a description rather than a bare link.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Is anything actually answering the query in the first sentence?
&lt;/h2&gt;

&lt;p&gt;This is content advice, but it is measurable. Open your page and read only the first sentence after the H1.&lt;/p&gt;

&lt;p&gt;If it says "Herbal remedies have been used for centuries..." you have written an introduction, not an answer. Compare:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most sore throats are viral and settle in 3–7 days. These remedies reduce the pain while that happens.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the &lt;a href="https://xg704863664.github.io/natural-remedies-for-sore-throat.html" rel="noopener noreferrer"&gt;sore throat page&lt;/a&gt;. The answer is in the first line; everything after it is detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Are the tables usable on a phone?
&lt;/h2&gt;

&lt;p&gt;Reference tables are the most common thing to break on mobile. A table that is 700px wide on a 390px screen forces horizontal scrolling for the whole page unless you wrap it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table-scroll"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;table&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.table-scroll&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;-webkit-overflow-scrolling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;touch&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;Cheap, and it stops the layout breaking on the device where most of your traffic will arrive.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would do in a different order
&lt;/h2&gt;

&lt;p&gt;If I did it again: &lt;strong&gt;build the hub page first&lt;/strong&gt;, then write content. I wrote 21 pages and only later created the page that ties them together, which meant the crawl path did not exist for weeks.&lt;/p&gt;

&lt;p&gt;The other thing I would not skip is the crawler-UA check. It takes two minutes and it is the only way to know for certain that what you published is what a crawler receives.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>static</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Three interactive calculators, zero dependencies: a static-site pattern</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:37:11 +0000</pubDate>
      <link>https://dev.to/xg704863664/three-interactive-calculators-zero-dependencies-a-static-site-pattern-4p7f</link>
      <guid>https://dev.to/xg704863664/three-interactive-calculators-zero-dependencies-a-static-site-pattern-4p7f</guid>
      <description>&lt;p&gt;Static sites have one obvious limitation: no server. So when I wanted to add calculators to a content site, the answer was not a backend — it was about 40 lines of vanilla JS per tool.&lt;/p&gt;

&lt;p&gt;Here are three patterns I ended up reusing, with the real tools linked so you can see them working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: unit conversion with a formula
&lt;/h2&gt;

&lt;p&gt;The first tool is a raised bed soil calculator. The whole thing is one formula — &lt;code&gt;volume = length × width × depth&lt;/code&gt; — plus unit conversion and rounding.&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;var&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m&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="na"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0254&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;U&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;len&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dep&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;m3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ...render&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part that matters is the guard clause. Users will type &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, or nothing, and a calculator that renders &lt;code&gt;NaN&lt;/code&gt; looks broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live version:&lt;/strong&gt; &lt;a href="https://xg704863664.github.io/raised-bed-soil-calculator.html" rel="noopener noreferrer"&gt;Raised Bed Soil Calculator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: rule-based sizing
&lt;/h2&gt;

&lt;p&gt;The second tool takes a count and applies rules. A chicken coop needs 2–3 sq ft per bird inside and 8–10 sq ft in the run, plus one nest box per five hens.&lt;/p&gt;

&lt;p&gt;The interesting design decision is exposing the rules as a dropdown rather than hardcoding one standard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2,8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Minimum (2 + 8 sq ft)&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"3,10"&lt;/span&gt; &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Comfortable (3 + 10 sq ft)&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"4,12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Generous (4 + 12 sq ft)&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;parts&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;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&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="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;coopSqFt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;inside&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encoding the rules in the markup rather than in JS means you can add a preset without touching the logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live version:&lt;/strong&gt; &lt;a href="https://xg704863664.github.io/chicken-coop-size-calculator.html" rel="noopener noreferrer"&gt;Chicken Coop Size Calculator&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: client-side filtering as a "tool"
&lt;/h2&gt;

&lt;p&gt;The third one is not a calculator at all — it is a search filter over a static table. 24 rows of food storage times, filtered as you type.&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;var&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="s2"&gt;tr[data-k]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-k&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&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="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is the &lt;code&gt;data-k&lt;/code&gt; attribute holding a pre-lowercased search key. It means you can search for text that is not visible in the row — synonyms, alternate spellings, unit names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live version:&lt;/strong&gt; &lt;a href="https://xg704863664.github.io/food-storage-time-lookup.html" rel="noopener noreferrer"&gt;Food Storage Time Lookup&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother on a static site
&lt;/h2&gt;

&lt;p&gt;Three reasons that turned out to matter more than I expected:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No build step.&lt;/strong&gt; The JS is inline in the page. Nothing to compile, nothing to version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with JS disabled.&lt;/strong&gt; The content is a real HTML table or a real list of numbers. The tool is an enhancement, not a requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools earn links.&lt;/strong&gt; People link to "this calculator" far more readily than to "this article". For a new site with no authority, that is the whole game.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The bit that is easy to get wrong
&lt;/h2&gt;

&lt;p&gt;Mobile. All three tools use a flex row with &lt;code&gt;flex-wrap: wrap&lt;/code&gt; and a full-width input below 620px. Without that, the labels and inputs fight for space and the tool becomes unusable on a phone — which is where most traffic actually is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;620px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.calc-row&lt;/span&gt; &lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.calc-row&lt;/span&gt; &lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you have built tools like this on a static host, I would be curious what patterns you reused. The three above cover most of what I needed, but I suspect there is a fourth one I have not hit yet.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>static</category>
      <category>css</category>
    </item>
    <item>
      <title>Your automation says it worked. Three ways it is lying to you.</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:59:37 +0000</pubDate>
      <link>https://dev.to/xg704863664/your-automation-says-it-worked-three-ways-it-is-lying-to-you-p2m</link>
      <guid>https://dev.to/xg704863664/your-automation-says-it-worked-three-ways-it-is-lying-to-you-p2m</guid>
      <description>&lt;p&gt;I spent a day chasing a problem that did not exist, because my scripts kept reporting success.&lt;/p&gt;

&lt;p&gt;Here are the three failure modes I hit, all in the same week, all silent.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The check that matched the wrong element
&lt;/h2&gt;

&lt;p&gt;I had a script that clicked a button, then verified the result by searching the page for a confirmation phrase.&lt;/p&gt;

&lt;p&gt;The verification matched. The action had not happened.&lt;/p&gt;

&lt;p&gt;Why: the page contained the confirmation phrase in &lt;em&gt;help text elsewhere on the page&lt;/em&gt;. My check was:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;already requested&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DONE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the page also contained a sentence like &lt;em&gt;'If you have already requested this, ...'&lt;/em&gt; in a tooltip. So the check passed on every run, whether or not anything happened.&lt;/p&gt;

&lt;p&gt;The fix is to assert on a &lt;strong&gt;mutually exclusive state&lt;/strong&gt;, not a substring:&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This page is not indexed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_INDEXED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;URL is on Google&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INDEXED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Substring checks are fine for reading. They are dangerous for asserting.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The element that was never in the viewport
&lt;/h2&gt;

&lt;p&gt;Scroll-then-click is a classic. But the order matters more than people expect:&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;// WRONG - coordinates are captured before the scroll settles&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;scrollIntoView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&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;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// clicks whatever is now at those coordinates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is boring: scroll, wait, &lt;em&gt;then&lt;/em&gt; read the bounding rect.&lt;/p&gt;

&lt;p&gt;I clicked the wrong element four times in a row because of this, and each time the script reported that it had clicked the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The state that persisted between runs
&lt;/h2&gt;

&lt;p&gt;This one is my favourite, because the tool worked perfectly and still gave me the wrong answer.&lt;/p&gt;

&lt;p&gt;I was reading a dashboard. It reported zero. I assumed the data had been reset.&lt;/p&gt;

&lt;p&gt;It had not. &lt;strong&gt;The dashboard persists your date-range filter server-side.&lt;/strong&gt; A previous run had set it to &lt;em&gt;Today&lt;/em&gt;. Every subsequent run was faithfully reporting today's data - which was legitimately zero - while I compared it against last month's totals from memory.&lt;/p&gt;

&lt;p&gt;The tool was correct. The context was wrong.&lt;/p&gt;

&lt;p&gt;Now every scraper I write logs the &lt;em&gt;state&lt;/em&gt; it is reading under, not just the values:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;period:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;| value:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// period: Today | value: 0      &amp;lt;-- instantly obvious&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The common thread
&lt;/h2&gt;

&lt;p&gt;All three are the same mistake in different clothes: &lt;strong&gt;treating a successful execution as evidence of a correct outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A click that dispatches is not a click that lands. A text match is not a state assertion. A number without its context is not a fact.&lt;/p&gt;

&lt;p&gt;The practical rule I now follow: for every action that matters, find an &lt;em&gt;independent&lt;/em&gt; signal that it happened - a fetch, a timestamp, a status from a different system. If the only evidence is the thing that did the action, you do not have evidence.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write practical growing and preserving guides at &lt;a href="https://xg704863664.github.io/herbal-home-remedies/" rel="noopener noreferrer"&gt;Herbal Home Remedies and Small-Space Growing&lt;/a&gt; - the automation above exists to run that site without me watching it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>testing</category>
      <category>javascript</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Shipping a static content site on GitHub Pages: the parts nobody documents</title>
      <dc:creator>xg704863664</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:24:11 +0000</pubDate>
      <link>https://dev.to/xg704863664/shipping-a-static-content-site-on-github-pages-the-parts-nobody-documents-3b1j</link>
      <guid>https://dev.to/xg704863664/shipping-a-static-content-site-on-github-pages-the-parts-nobody-documents-3b1j</guid>
      <description>&lt;p&gt;I built a 20-page static content site on GitHub Pages - no local git checkout, no build step, no framework. Everything went through the web UI and a scripted browser.&lt;/p&gt;

&lt;p&gt;Here is what actually mattered, including the parts that cost me hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The branch &lt;em&gt;is&lt;/em&gt; the deploy
&lt;/h2&gt;

&lt;p&gt;There is no deploy button. Push to the branch configured in Settings -&amp;gt; Pages and GitHub rebuilds. The practical consequence nobody mentions: &lt;strong&gt;after a commit, the live URL 404s for 30-90 seconds while it rebuilds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you verify a file immediately after committing it, you will get a false negative. I hit this verifying a Search Console token file - first check 404, second check (a minute later) 200 with the right contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;/new/main&lt;/code&gt; only works for files that do not exist
&lt;/h2&gt;

&lt;p&gt;The web editor URL &lt;code&gt;github.com/&amp;lt;user&amp;gt;/&amp;lt;repo&amp;gt;/new/main&lt;/code&gt; is the natural way to create a file programmatically. But if the file already exists, the flow does not silently overwrite - it errors, and if you are driving the UI blind you may not notice.&lt;/p&gt;

&lt;p&gt;Editing needs a different route: &lt;code&gt;github.com/&amp;lt;user&amp;gt;/&amp;lt;repo&amp;gt;/edit/main/&amp;lt;path&amp;gt;&lt;/code&gt;, then select-all and replace.&lt;/p&gt;

&lt;p&gt;This cost me a stale &lt;code&gt;sitemap.xml&lt;/code&gt; that I thought I had updated for several hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. IndexNow is the fastest indexing lever you have
&lt;/h2&gt;

&lt;p&gt;IndexNow is a simple protocol: host a text file containing a key, then POST your URLs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. key file at https://example.com/&amp;lt;key&amp;gt;.txt  (contents: the key)&lt;/span&gt;
&lt;span class="c"&gt;# 2. POST:&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.indexnow.org/indexnow &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"host":"example.com","key":"&amp;lt;key&amp;gt;","keyLocation":"https://example.com/&amp;lt;key&amp;gt;.txt","urlList":["https://example.com/page/"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bing, Yandex, Seznam and Naver consume it. A &lt;code&gt;202 Accepted&lt;/code&gt; means it was queued.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha&lt;/strong&gt;: the key file must be reachable &lt;em&gt;before&lt;/em&gt; you submit, or the submission is rejected. Verify it returns 200 first.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Search Console verification for a subdomain-hosted site
&lt;/h2&gt;

&lt;p&gt;If you are on &lt;code&gt;*.github.io&lt;/code&gt;, you cannot do DNS verification. Use the &lt;strong&gt;HTML file&lt;/strong&gt; method instead - you can commit the token file to the repo root.&lt;/p&gt;

&lt;p&gt;The file contents are exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-site-verification: google&amp;lt;token&amp;gt;.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And remember point 1 - check it is live before clicking Verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Request indexing has a daily quota and a silent failure mode
&lt;/h2&gt;

&lt;p&gt;URL Inspection -&amp;gt; Request indexing pushes a URL into the priority crawl queue. In my case Googlebot arrived &lt;strong&gt;14 minutes later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a daily quota (around 10 URLs). Exceeding it returns a quota message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check that the request actually registered.&lt;/strong&gt; If your automation checks the wrong element, you can keep requesting indexing for days and never notice that nothing happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I lost two URLs to exactly that. I only found out by auditing crawl status page by page.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. FAQPage structured data for question-shaped queries
&lt;/h2&gt;

&lt;p&gt;If your content answers specific questions, add &lt;code&gt;FAQPage&lt;/code&gt; JSON-LD. It is cheap and it targets the long tail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAQPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mainEntity&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How much space does one chicken need?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acceptedAnswer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About 2-3 square feet inside the coop.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}]}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the answers short and literal - they are competing for a snippet, not for a full read.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What I would do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commit the sitemap last.&lt;/strong&gt; Every page you add invalidates it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not trust a UI action you cannot verify.&lt;/strong&gt; Every state change needs an independent check (a fetch, a crawl status, a log line).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexing is not ranking.&lt;/strong&gt; Getting crawled took minutes; getting indexed is taking days; ranking is a different problem entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The site in question
&lt;/h2&gt;

&lt;p&gt;The result is a set of practical guides on herbal remedies, small-space growing and keeping a few chickens - &lt;a href="https://xg704863664.github.io/herbal-home-remedies/" rel="noopener noreferrer"&gt;Herbal Home Remedies and Small-Space Growing&lt;/a&gt;. The content is deliberately plain: amounts, temperatures, and the caveats the sales pages leave out.&lt;/p&gt;

&lt;p&gt;If you are doing something similar, the whole thing is a single repo with static HTML - no build, no dependencies, and it deploys from a commit.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>github</category>
      <category>seo</category>
      <category>static</category>
    </item>
  </channel>
</rss>
