<?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: Oleg Kolesnykov</title>
    <description>The latest articles on DEV Community by Oleg Kolesnykov (@ok1mind).</description>
    <link>https://dev.to/ok1mind</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%2F4127993%2F758e41a7-8aa8-460c-92f8-fc38294ed0e2.png</url>
      <title>DEV Community: Oleg Kolesnykov</title>
      <link>https://dev.to/ok1mind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ok1mind"/>
    <language>en</language>
    <item>
      <title>The new-site boost ended. Three small tools then beat my 312 content pages in a week</title>
      <dc:creator>Oleg Kolesnykov</dc:creator>
      <pubDate>Fri, 23 Oct 2026 10:28:56 +0000</pubDate>
      <link>https://dev.to/ok1mind/the-new-site-boost-ended-three-small-tools-then-beat-my-312-content-pages-in-a-week-1p82</link>
      <guid>https://dev.to/ok1mind/the-new-site-boost-ended-three-small-tools-then-beat-my-312-content-pages-in-a-week-1p82</guid>
      <description>&lt;p&gt;This is a follow-up to &lt;a href="https://dev.to/ok1mind/i-ran-an-seo-experiment-on-my-side-project-and-broke-my-own-control-group-40pg"&gt;my last post&lt;/a&gt;, where I rewrote 78 meta descriptions and ruined my own control group. The project is a tarot reference in four languages: &lt;a href="https://tarot-magic.com" rel="noopener noreferrer"&gt;https://tarot-magic.com&lt;/a&gt;. It has 312 card pages (78 cards in each language) and a handful of interactive pages.&lt;/p&gt;

&lt;p&gt;I pulled fresh Search Console data this week. Two things stood out, and neither of them was the experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boost I mistook for ranking
&lt;/h2&gt;

&lt;p&gt;Two equal 28-day windows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Aug 24 - Sep 20&lt;/th&gt;
&lt;th&gt;Sep 21 - Oct 18&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clicks&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impressions&lt;/td&gt;
&lt;td&gt;9,100&lt;/td&gt;
&lt;td&gt;3,030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg. position&lt;/td&gt;
&lt;td&gt;19.0&lt;/td&gt;
&lt;td&gt;32.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same clicks, a third of the impressions. The daily chart explained it. From August 25 to August 31 the site got roughly 700 to 970 impressions a day at an average position of 12 to 17. On September 7 it was down to 239, and from September 10 on it mostly stayed between 50 and 110 a day.&lt;/p&gt;

&lt;p&gt;That week in late August was not my site being good. Google tried the new pages high, saw how people reacted, and moved them back. People call it the new site honeymoon. I had read about it and still spent September comparing every number against those few days.&lt;/p&gt;

&lt;p&gt;If you launch something, write down your first two weeks and then stop looking at them. They are a test, not a baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three tools, one week
&lt;/h2&gt;

&lt;p&gt;On October 10 I added two small interactive pages: a love reading with three cards and a page that pulls one random card. On October 16 a birth card calculator went live. You enter a date of birth and it adds the digits down to one of the 22 Major Arcana. Each exists in four languages, so 12 new URLs in total.&lt;/p&gt;

&lt;p&gt;Then came the week of October 12 to 18:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Oct 5 - 11&lt;/th&gt;
&lt;th&gt;Oct 12 - 18&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Impressions, whole site&lt;/td&gt;
&lt;td&gt;601&lt;/td&gt;
&lt;td&gt;1,238&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clicks, whole site&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Impressions doubled in a week. Here is the 1,238 split by page type:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page type&lt;/th&gt;
&lt;th&gt;Pages with impressions&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New tools&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Card pages&lt;/td&gt;
&lt;td&gt;36 of 312&lt;/td&gt;
&lt;td&gt;284&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Older tools (3-card reading, yes/no, card of the day)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;235&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everything else (home, blog, course)&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;319&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Russian version of the birth card calculator alone got 196 impressions and 3 clicks at an average position of 23.5. It had been live for three days. No single card page got more than 34 impressions that week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think it happened
&lt;/h2&gt;

&lt;p&gt;Three days of data is thin, so this is a guess. My best explanation is intent. A card page answers "what does the Ten of Swords mean", and that question already has plenty of good answers on sites that are years older than mine. "Calculate my birth arcana from my date of birth" asks the page to do something. A page that actually does it seems to get a chance much sooner, even on a young domain.&lt;/p&gt;

&lt;p&gt;The queries point the same way. Most of the calculator's impressions came from dozens of phrasings of one request (find my arcana by date of birth, arcana calculator, calculate it online), each with one or two impressions.&lt;/p&gt;

&lt;p&gt;Position 23.5 is page three, so there is nothing to celebrate yet. Still, most card pages needed weeks to get that far, and many of them never did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm changing
&lt;/h2&gt;

&lt;p&gt;I stopped comparing against August. The baseline is now the first half of October. New work goes to small tools that answer a "do something" query, and to linking those tools from articles I already have; the card pages stay as they are.&lt;/p&gt;

&lt;p&gt;In two weeks the calculator will have a full 14 days of data. I'll run the same split again, and if it falls back to card page level, I'll write that too.&lt;/p&gt;

&lt;p&gt;Has anyone seen tool pages on a new domain pick up faster than content pages? I'm curious whether it holds after the first few weeks.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>analytics</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>I ran an SEO experiment on my side project and broke my own control group</title>
      <dc:creator>Oleg Kolesnykov</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:27:53 +0000</pubDate>
      <link>https://dev.to/ok1mind/i-ran-an-seo-experiment-on-my-side-project-and-broke-my-own-control-group-40pg</link>
      <guid>https://dev.to/ok1mind/i-ran-an-seo-experiment-on-my-side-project-and-broke-my-own-control-group-40pg</guid>
      <description>&lt;p&gt;In September I had a weird problem with my side project, a tarot card reference in four languages: &lt;a href="https://tarot-magic.com" rel="noopener noreferrer"&gt;https://tarot-magic.com&lt;/a&gt;. The Ukrainian card pages sat at positions 6-9 in Google and got almost no clicks. Over three months they had 2,614 impressions and 2 clicks.&lt;/p&gt;

&lt;p&gt;On September 21 I rewrote all 78 Ukrainian meta descriptions. The old ones were keyword dumps cut off mid-sentence. The new ones are one plain sentence about what the page actually covers. I left the other three languages alone as a control and promised myself to wait two weeks before looking.&lt;/p&gt;

&lt;p&gt;Two weeks are up. Here is what happened, including the part where I ruined the experiment myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;I compared two equal 14-day windows in Search Console, filtered to &lt;code&gt;/uk/card/&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Sep 7 - Sep 20&lt;/th&gt;
&lt;th&gt;Sep 21 - Oct 4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Impressions&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;td&gt;463&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clicks&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg. position&lt;/td&gt;
&lt;td&gt;11.2&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Impressions doubled while average position got worse. That looks contradictory until you check the page list: Google started showing pages that had no impressions before, and they enter at positions 20-35. They drag the average down even though part of the existing pages held their positions.&lt;/p&gt;

&lt;p&gt;If I keep only pages averaging position 10 or better, I get 157 impressions and 2 clicks, a CTR of 1.3%. The previous window under the same filter had 197 impressions and zero clicks.&lt;/p&gt;

&lt;p&gt;1.3% is roughly what you would expect at positions 6-8 on mobile. But it is two clicks. I am not going to claim anything from two clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I broke it
&lt;/h2&gt;

&lt;p&gt;Before starting I wrote down one rule: compare CTR only if positions stay stable, because CTR at position 7 and at position 30 are different metrics.&lt;/p&gt;

&lt;p&gt;Positions did not stay stable. The single biggest Ukrainian page in the top 10, "Six of Wands", went from 7.4 to 37.5 in exactly this window.&lt;/p&gt;

&lt;p&gt;Worse, on the same day the descriptions changed, an FAQ block was added to all 312 card pages in all four languages. It started as a six-page pilot and got rolled out everywhere at once. So the "control" languages were not a control anymore. Russian card pages dropped from 379 impressions to 133 in the same period, and I have no way to tell whether that was the FAQ, normal ranking churn for a young domain, or something else.&lt;/p&gt;

&lt;p&gt;Lesson for next time: a control group must not be touched by anything, including changes that look harmless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The accidental winner
&lt;/h2&gt;

&lt;p&gt;The best result came from a page that was not part of the experiment. On September 20 the French "card of the day" page got the main local query, "Tarot du jour", added to its title. Over the next two weeks it got 18 impressions and 5 clicks. That is half of all clicks on the site for the period.&lt;/p&gt;

&lt;p&gt;My current guess is that the title decides whether someone looks at the result at all, and the description only matters after that. That is the next thing I want to test, with a control group I will not touch this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Yandex
&lt;/h2&gt;

&lt;p&gt;Yandex had excluded 76 of 88 Russian pages as "low value". On October 1 I sent all Russian URLs for recrawl through Yandex Webmaster. Pages in search: 12 on Oct 1, 21 on Oct 2, 30 on Oct 5. No new links, no analytics changes, just a fresh visit from the crawler. The same update on Oct 5 dropped 20 English card pages with the same "low value" status, all last crawled around Sep 21-23. So if you work with Yandex, recrawl requests seem to matter more than I expected.&lt;/p&gt;

&lt;p&gt;Next check is mid-October. If anyone has run title A/B tests on a small site, I would love to hear how many impressions you needed before the difference was real.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>analytics</category>
      <category>react</category>
    </item>
    <item>
      <title>I prerendered my React SPA for SEO. I forgot the links.</title>
      <dc:creator>Oleg Kolesnykov</dc:creator>
      <pubDate>Wed, 16 Sep 2026 12:41:43 +0000</pubDate>
      <link>https://dev.to/ok1mind/i-prerendered-my-react-spa-for-seo-i-forgot-the-links-2d3l</link>
      <guid>https://dev.to/ok1mind/i-prerendered-my-react-spa-for-seo-i-forgot-the-links-2d3l</guid>
      <description>&lt;p&gt;My site had been live for a month. Google had indexed all 352 pages. Titles matched&lt;br&gt;
search queries exactly. Structured data was in place. hreflang covered four locales.&lt;/p&gt;

&lt;p&gt;Then I ran a site audit and got 24 errors, all the same:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Page has no outgoing links&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The homepage. Every locale homepage. Every tool page. And — the one that actually&lt;br&gt;
hurt — the hub page that is supposed to link to all 78 tarot cards.&lt;/p&gt;

&lt;p&gt;That last one made no sense. I could open it in my browser and see 78 links right&lt;br&gt;
there. So I did what I should have done much earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://tarot-magic.com/uk/card-meanings | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;a [^&amp;gt;]*href='&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note the &lt;code&gt;grep -o ... | wc -l&lt;/code&gt; — not &lt;code&gt;grep -c&lt;/code&gt;. Most HTML ships minified on a&lt;br&gt;
single line, so &lt;code&gt;grep -c&lt;/code&gt; counts &lt;em&gt;lines containing a match&lt;/em&gt; and cheerfully&lt;br&gt;
reports &lt;code&gt;1&lt;/code&gt; no matter how many links you have. I got that wrong the first time&lt;br&gt;
and briefly convinced myself the fix hadn't worked.)&lt;/p&gt;

&lt;p&gt;Zero. Then in the browser console, on the same page:&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="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;a[href]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
&lt;span class="c1"&gt;// 56&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifty-six links that existed for humans and did not exist for crawlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I had actually built
&lt;/h2&gt;

&lt;p&gt;The site is a React SPA. At some point I added a prerender step so crawlers would&lt;br&gt;
get real content instead of an empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&lt;/code&gt;. It worked — page text was&lt;br&gt;
in the HTML, word counts were healthy, pages ranked.&lt;/p&gt;

&lt;p&gt;Here is what the prerendered body looked like:&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;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;noscript&amp;gt;&lt;/span&gt;You need to enable JavaScript to run this app.&lt;span class="nt"&gt;&amp;lt;/noscript&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&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;"pre-bg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pre-wrap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tarot Card Meanings&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pre-lead"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;A complete encyclopedia of all 78 cards&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Frequently asked questions&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;How many cards are in a tarot deck?&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;A standard deck has 78 cards: 22 Major Arcana and 56 Minor Arcana…&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Text, headings, FAQ copy. All of it useful. And not a single &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I had solved "crawlers can't read my content" and never noticed I hadn't solved&lt;br&gt;
"crawlers can't navigate my site."&lt;/p&gt;
&lt;h2&gt;
  
  
  The part that made it obvious
&lt;/h2&gt;

&lt;p&gt;The same site has a blog. Articles are generated by a different code path, and&lt;br&gt;
those pages looked like this:&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;"pre-bg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pre-wrap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pre-article"&lt;/span&gt;&lt;span class="nt"&gt;&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;"/uk/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/a&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;"/uk/blog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Journal&lt;span class="nt"&gt;&amp;lt;/a&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;"#the-element-of-fire"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;The Element of Fire&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;26 links, 20 of them internal. Same wrapper, same mechanism — someone (me, months&lt;br&gt;
earlier) had put links into the article template and not into anything else.&lt;/p&gt;

&lt;p&gt;So this was never an architecture problem. The machinery was already there. One&lt;br&gt;
template had links; the other twelve didn't.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why it mattered more than I expected
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Google renders JavaScript&lt;/strong&gt;, so my links weren't invisible to it. But rendering&lt;br&gt;
is a second, deferred pass. Google crawls the raw HTML first, builds a link graph&lt;br&gt;
from it, and comes back to render later. For a month, the link graph Google built&lt;br&gt;
from my site was: 352 pages, no edges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bing and Yandex barely render JS at all.&lt;/strong&gt; Over the same period:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search engine&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;10,820&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bing&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yandex&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I had been telling myself that was a new-domain problem. Some of it was. But those&lt;br&gt;
crawlers were also looking at a site where nothing linked to anything, discovering&lt;br&gt;
pages only through &lt;code&gt;sitemap.xml&lt;/code&gt;, and reasonably concluding there was no structure&lt;br&gt;
worth indexing.&lt;/p&gt;

&lt;p&gt;My average position across all queries was 23.9 — page three. Internal linking is&lt;br&gt;
one of the two levers that move that number. I had turned it off without knowing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;No architecture change. Put links into the prerendered HTML:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Header and footer nav on every template&lt;/strong&gt; — the global one, five minutes of
real work, fixes every page at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hub page lists its 78 cards as actual anchors&lt;/strong&gt; — this is the page whose
entire job is distributing link equity to 312 card pages across four locales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Card pages link to their siblings&lt;/strong&gt; — previously they had 3 links, all
breadcrumbs.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Results, same day
&lt;/h2&gt;

&lt;p&gt;I re-ran the audit a few hours after deploying:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Health score&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Only one incoming internal link"&lt;/td&gt;
&lt;td&gt;36 pages&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internal links discovered&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,020&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10,056&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow pages&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row was a surprise — I hadn't touched performance. My guess is the crawler&lt;br&gt;
was spending its budget differently once it could actually traverse the site, but&lt;br&gt;
I can't prove that, so take it as a coincidence until someone shows me otherwise.&lt;/p&gt;

&lt;p&gt;Ranking effects are still pending. Google has to recrawl 312 pages and rebuild the&lt;br&gt;
graph; that takes weeks, not hours. I'll know in a month whether it moved anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  The check that would have saved me a month
&lt;/h2&gt;

&lt;p&gt;If you prerender, statically generate, or SSR a JS app, run this before you trust it:&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="k"&gt;for &lt;/span&gt;path &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="s2"&gt;"about"&lt;/span&gt; &lt;span class="s2"&gt;"products"&lt;/span&gt; &lt;span class="s2"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%-20s %s\n'&lt;/span&gt; &lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://yoursite.com/&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;a [^&amp;gt;]*href='&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare each number to &lt;code&gt;document.querySelectorAll('a[href]').length&lt;/code&gt; in the browser&lt;br&gt;
on the same URL. If the browser number is much bigger, your navigation is invisible&lt;br&gt;
to anything that doesn't run JavaScript.&lt;/p&gt;

&lt;p&gt;I checked my word counts religiously. I never checked my link counts. Word count&lt;br&gt;
tells you a crawler can &lt;em&gt;read&lt;/em&gt; the page. Link count tells you it can &lt;em&gt;leave&lt;/em&gt; it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The site in question is &lt;a href="https://tarot-magic.com" rel="noopener noreferrer"&gt;tarot-magic.com&lt;/a&gt; — a tarot&lt;br&gt;
reference in four languages. If you want to see the check pass, run the command&lt;br&gt;
above against &lt;code&gt;/uk/card-meanings&lt;/code&gt;. It returns 105 now.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>seo</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
