<?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: Christina Sanchez</title>
    <description>The latest articles on DEV Community by Christina Sanchez (@christina_sanchez_f16f40a).</description>
    <link>https://dev.to/christina_sanchez_f16f40a</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3833566%2F7dd074d2-e194-4f21-86ca-4e0b00360750.png</url>
      <title>DEV Community: Christina Sanchez</title>
      <link>https://dev.to/christina_sanchez_f16f40a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christina_sanchez_f16f40a"/>
    <language>en</language>
    <item>
      <title>83% of Brokers Block US Clients — I Analyzed 345 Brokers to Find Out Why</title>
      <dc:creator>Christina Sanchez</dc:creator>
      <pubDate>Mon, 06 Apr 2026 20:55:41 +0000</pubDate>
      <link>https://dev.to/christina_sanchez_f16f40a/83-of-brokers-block-us-clients-i-analyzed-345-brokers-to-find-out-why-14kl</link>
      <guid>https://dev.to/christina_sanchez_f16f40a/83-of-brokers-block-us-clients-i-analyzed-345-brokers-to-find-out-why-14kl</guid>
      <description>&lt;p&gt;I built BrokerRank — a broker comparison platform with 345+ brokers in a PostgreSQL database. While adding geo-restriction data, I discovered something surprising: 83% of online brokers don't accept US clients.&lt;/p&gt;

&lt;p&gt;That makes the US the most restricted non-sanctioned country for online trading.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;345 brokers analyzed&lt;/li&gt;
&lt;li&gt;287 (83%) block US clients&lt;/li&gt;
&lt;li&gt;Only 58 accept Americans&lt;/li&gt;
&lt;li&gt;100% block sanctioned countries (Iran, North Korea, Syria)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dodd-Frank Act&lt;/strong&gt; — requires CFTC/NFA registration, $20M+ capital requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FATCA&lt;/strong&gt; — forces foreign brokers to report US clients to the IRS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage limits&lt;/strong&gt; — CFTC caps at 1:50 vs 1:500 offshore&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Litigation risk&lt;/strong&gt; — US fines are 10x higher than EU&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The tech
&lt;/h2&gt;

&lt;p&gt;I stored &lt;code&gt;restrictedCountries&lt;/code&gt; as a PostgreSQL text array on each broker, then built a research page that queries this data in real-time with Prisma.&lt;/p&gt;

&lt;p&gt;Country-specific ranking pages (&lt;code&gt;/brokers/iraq&lt;/code&gt;, &lt;code&gt;/brokers/usa&lt;/code&gt;) automatically filter out brokers that don't accept clients from that country.&lt;/p&gt;

&lt;p&gt;Full interactive research page: &lt;a href="https://brokerrank.net/research/us-broker-restrictions" rel="noopener noreferrer"&gt;brokerrank.net/research/us-broker-restrictions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built with Next.js 16, PostgreSQL (Neon), Prisma, deployed on Vercel.&lt;/p&gt;




&lt;p&gt;I also published 5 more data studies if you're curious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://brokerrank.net/research/broker-regulation-analysis" rel="noopener noreferrer"&gt;Which regulators do traders trust most?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brokerrank.net/research/retail-loss-rates" rel="noopener noreferrer"&gt;71% of retail traders lose money&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brokerrank.net/research/global-leverage-map" rel="noopener noreferrer"&gt;Global leverage map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brokerrank.net/research/trading-platform-analysis" rel="noopener noreferrer"&gt;MT4 vs MT5 vs Proprietary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://brokerrank.net/research/minimum-deposit-analysis" rel="noopener noreferrer"&gt;Minimum deposit analysis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>data</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How I Got 33K Google Impressions in 2 Weeks with Programmatic SEO</title>
      <dc:creator>Christina Sanchez</dc:creator>
      <pubDate>Wed, 01 Apr 2026 06:13:20 +0000</pubDate>
      <link>https://dev.to/christina_sanchez_f16f40a/how-i-got-33k-google-impressions-in-2-weeks-with-programmatic-seo-fk8</link>
      <guid>https://dev.to/christina_sanchez_f16f40a/how-i-got-33k-google-impressions-in-2-weeks-with-programmatic-seo-fk8</guid>
      <description>&lt;p&gt;Two weeks ago I shared how I built a &lt;a href="https://dev.to/christina_sanchez_f16f40a/how-i-built-a-10000-page-seo-site-with-nextjs-and-postgresql-3ipp"&gt;10,000+ page SEO site with Next.js and PostgreSQL&lt;/a&gt;. Today I'm back with the results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; 33,467 impressions, 70 clicks, pages ranking on page 1 of Google — all within 14 days of deploying to Vercel. No paid ads. No backlinks. Just programmatic SEO done right.&lt;/p&gt;

&lt;p&gt;Here's exactly what worked, what didn't, and what I'd do differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers (first 14 days)
&lt;/h2&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;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total impressions&lt;/td&gt;
&lt;td&gt;33,467&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total clicks&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average CTR&lt;/td&gt;
&lt;td&gt;0.21%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average position&lt;/td&gt;
&lt;td&gt;23.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexed pages&lt;/td&gt;
&lt;td&gt;10,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Countries reached&lt;/td&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak daily clicks&lt;/td&gt;
&lt;td&gt;22 (day 12)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The growth curve looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 1:&lt;/strong&gt; 0 impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; 2 impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; 132 impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 5:&lt;/strong&gt; 705 impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 10:&lt;/strong&gt; 4,507 impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 11:&lt;/strong&gt; 12,129 impressions (something clicked)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That jump from 700 to 12K impressions in one day? That's when Google finished crawling the bulk of my sitemap. More on that below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack (Quick Recap)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; (App Router) on Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; (Neon) + Prisma ORM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISR&lt;/strong&gt; with 24-hour revalidation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;300+ entities&lt;/strong&gt; in the database, each generating 8+ pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every entity generates: a main review page, a pricing breakdown, feature details, compliance analysis, and alternatives page. Plus head-to-head comparison pages, category ranking pages, country-specific pages, glossary terms, and educational guides.&lt;/p&gt;

&lt;p&gt;Total: &lt;strong&gt;10,000+ unique URLs&lt;/strong&gt;, all from one database.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Moved the Needle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Sitemap + IndexNow = Fast Crawling
&lt;/h3&gt;

&lt;p&gt;The single most impactful thing I did was submit a comprehensive sitemap on day 1 and ping all IndexNow endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sitemap generated dynamically from DB&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rankings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comparisons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;glossary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isActive&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="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;not&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="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// ... more queries&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I submitted all 10K URLs to Bing, Yandex, Naver, and Seznam via IndexNow on launch day. Google doesn't support IndexNow directly, but Bing shares crawl signals with Google.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INDEXNOW_ENDPOINTS&lt;/span&gt; &lt;span class="o"&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;url&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://www.bing.com/indexnow&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Yandex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&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://yandex.com/indexnow&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Naver&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;url&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://searchadvisor.naver.com/indexnow&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Seznam&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&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://search.seznam.cz/indexnow&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="c1"&gt;// Submit in batches of 10,000 (IndexNow limit)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;INDEXNOW_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;keyLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;DOMAIN&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;INDEXNOW_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;urlList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&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;endpoint&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;Content-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;application/json; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;&lt;strong&gt;Result:&lt;/strong&gt; Google started crawling within hours. By day 3, hundreds of pages were indexed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Structured Data on Every Page
&lt;/h3&gt;

&lt;p&gt;Every page type has its own schema.org markup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review pages:&lt;/strong&gt; &lt;code&gt;Review&lt;/code&gt; + &lt;code&gt;AggregateRating&lt;/code&gt; + &lt;code&gt;FAQPage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rankings:&lt;/strong&gt; &lt;code&gt;ItemList&lt;/code&gt; + &lt;code&gt;Article&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparisons:&lt;/strong&gt; &lt;code&gt;Article&lt;/code&gt; + &lt;code&gt;FAQPage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data pages:&lt;/strong&gt; &lt;code&gt;Dataset&lt;/code&gt; + &lt;code&gt;Table&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All pages:&lt;/strong&gt; &lt;code&gt;BreadcrumbList&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviewSchema&lt;/span&gt; &lt;span class="o"&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;Review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;itemReviewed&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;Product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;reviewRating&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;Rating&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ratingValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bestRating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;author&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;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MySite&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google Search Console flagged some issues early on (duplicate FAQPage schemas, missing breadcrumb items) — I fixed them within the first week. &lt;strong&gt;Monitor GSC daily in the early days.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Programmatic but Not Thin
&lt;/h3&gt;

&lt;p&gt;This is where most programmatic SEO projects fail. They generate thousands of pages with template text and zero unique value.&lt;/p&gt;

&lt;p&gt;Every page on my site has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real data&lt;/strong&gt; — actual pricing, features, compliance status from the DB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Computed comparisons&lt;/strong&gt; — "pricing is 40% lower than the industry average"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic FAQs&lt;/strong&gt; — 15 questions per entity, each using real data points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual internal links&lt;/strong&gt; — related rankings, comparisons, glossary terms
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// FAQ example — uses actual data, not filler text&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`What are &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'s fees?`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;answer&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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; charges &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; 
    &lt;span class="s2"&gt;`a base fee of $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no base fee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&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;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;premium&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;` plus $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;premium&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; premium`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, with no additional premium&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;every page must answer a question that no other page on the internet answers in exactly that way.&lt;/strong&gt; A pricing page with actual numbers is valuable. The same template with placeholder text is spam.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. URL Architecture Matters
&lt;/h3&gt;

&lt;p&gt;My URL structure creates natural topical clusters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/product/acme            → main review
/product/acme/pricing    → pricing breakdown
/product/acme/features   → feature details
/product/acme/compliance → regulatory status
/product/acme/alternatives → competitors

/compare/acme-vs-globex     → head-to-head
/rankings/best-in-category  → category ranking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each cluster interlinks heavily. The main review links to all sub-pages. Sub-pages link back and to related rankings. Rankings link to individual product pages.&lt;/p&gt;

&lt;p&gt;Google understands this as topical authority. When one page in a cluster ranks, it lifts the others.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Data Tells Me
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query Types That Ranked Fastest
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query pattern&lt;/th&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Page Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Brand A vs Brand B"&lt;/td&gt;
&lt;td&gt;2–8&lt;/td&gt;
&lt;td&gt;Compare pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Brand + pricing/compliance"&lt;/td&gt;
&lt;td&gt;5–12&lt;/td&gt;
&lt;td&gt;Sub-pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Country-specific ("best X in Estonia")&lt;/td&gt;
&lt;td&gt;6–9&lt;/td&gt;
&lt;td&gt;Country pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Niche feature rankings&lt;/td&gt;
&lt;td&gt;8–16&lt;/td&gt;
&lt;td&gt;Ranking pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Comparison pages ranked fastest.&lt;/strong&gt; Long-tail, low competition, high intent. If you're building a comparison site, these are your quick wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Split
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Clicks&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;Desktop&lt;/td&gt;
&lt;td&gt;39 (56%)&lt;/td&gt;
&lt;td&gt;28,254 (84%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile&lt;/td&gt;
&lt;td&gt;29 (41%)&lt;/td&gt;
&lt;td&gt;5,160 (15%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Desktop dominates impressions for B2B/comparison queries. But mobile has &lt;strong&gt;3.5x higher CTR&lt;/strong&gt; — mobile users who find you are more likely to click.&lt;/p&gt;

&lt;p&gt;Traffic came from 50+ countries, with US leading (12K impressions) followed by UK, Southeast Asia, and India. &lt;/p&gt;




&lt;h2&gt;
  
  
  Mistakes I Made
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Blocking &lt;code&gt;/_next/&lt;/code&gt; in robots.txt
&lt;/h3&gt;

&lt;p&gt;I added &lt;code&gt;/_next/&lt;/code&gt; to my robots.txt disallow list, thinking it was just build artifacts. Wrong. Google needs access to JS and CSS bundles to render pages properly. This may have slowed initial indexing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Remove &lt;code&gt;/_next/&lt;/code&gt; from disallow. Only block truly private routes like &lt;code&gt;/api/&lt;/code&gt; and redirect handlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Duplicate Structured Data
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;FAQSection&lt;/code&gt; React component rendered its own JSON-LD schema internally. Several pages also rendered FAQ schema inline in the server component. Result: duplicate FAQPage warnings across thousands of pages in GSC.&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="c1"&gt;// The component had renderSchema={true} by default&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FAQSection&lt;/span&gt; &lt;span class="na"&gt;faqs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;faqs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Pages that already had inline schema needed:&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FAQSection&lt;/span&gt; &lt;span class="na"&gt;faqs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;faqs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;renderSchema&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&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;strong&gt;Lesson:&lt;/strong&gt; When you have a component that renders structured data, make sure the parent page doesn't render the same schema. Sounds obvious — but at scale, it's easy to miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Breadcrumb Without URL
&lt;/h3&gt;

&lt;p&gt;My breadcrumb component allowed intermediate crumbs without an &lt;code&gt;href&lt;/code&gt;. Google requires the &lt;code&gt;item&lt;/code&gt; (URL) field for every breadcrumb element except the last one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bug: "Category" has no href but isn't the last item&lt;/span&gt;
&lt;span class="nx"&gt;crumbs&lt;/span&gt;&lt;span class="o"&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Glossary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/glossary&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;        &lt;span class="c1"&gt;// Missing href!&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Current Page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;     &lt;span class="c1"&gt;// Last item — OK without href&lt;/span&gt;
&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This caused "Missing field item" errors across hundreds of pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Not Monitoring GSC from Day 1
&lt;/h3&gt;

&lt;p&gt;I waited a week before checking Google Search Console. By then I'd accumulated structured data errors across thousands of pages. &lt;strong&gt;Set up GSC monitoring before you launch.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Bits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ISR Configuration
&lt;/h3&gt;

&lt;p&gt;Every page uses Incremental Static Regeneration with a 24-hour window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="c1"&gt;// 24h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means pages are static (fast, cacheable) but refresh daily with fresh data. Google sees fast load times AND fresh content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meta Description Templates That Work
&lt;/h3&gt;

&lt;p&gt;Generic descriptions kill CTR. I optimized every template to include specific data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before (generic)&lt;/span&gt;
&lt;span class="s2"&gt;`Compare &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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and &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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; on pricing, features and more.`&lt;/span&gt;

&lt;span class="c1"&gt;// After (specific, with ratings)&lt;/span&gt;
&lt;span class="s2"&gt;`&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;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; vs &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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — which is better in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;? `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="s2"&gt;`Pricing, features, compliance compared side by side. `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="s2"&gt;`Scores: &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;name&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;ratingA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/5 vs &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="nx"&gt;name&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;ratingB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/5.`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "after" version includes the year (freshness signal), a question (matches search intent), and ratings (rich snippet potential).&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal Linking at Scale
&lt;/h3&gt;

&lt;p&gt;Every page has contextual internal links generated from the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sub-pages link to relevant category rankings&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RELATED_RANKINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pricing&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="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rankings/lowest-cost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lowest Cost&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;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rankings/best-free-tier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Best Free Tier&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;features&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="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rankings/most-features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Most Features&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;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rankings/best-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Best API&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus glossary term auto-linking in long-form content, related comparisons, and "explore more" sections. Every internal link is contextual — never random.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;At 2 weeks in, the site is in Google's "sandbox" — new domains get limited trust. Based on what I've seen from others doing programmatic SEO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Month 1–2:&lt;/strong&gt; Impressions grow as more pages get indexed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 2–3:&lt;/strong&gt; Positions start improving as Google builds trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 3–6:&lt;/strong&gt; Organic traffic hockey stick (if content is genuinely useful)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm focusing on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building backlinks through original datasets and data visualizations&lt;/li&gt;
&lt;li&gt;Monitoring which page types rank fastest and doubling down&lt;/li&gt;
&lt;li&gt;Fixing any new GSC issues within 24 hours&lt;/li&gt;
&lt;li&gt;Optimizing meta descriptions for pages that have impressions but low CTR&lt;/li&gt;
&lt;/ol&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Programmatic SEO works if every page has unique value.&lt;/strong&gt; Template text across 10K pages = spam. Real data across 10K pages = authority.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Submit your sitemap + IndexNow on day 1.&lt;/strong&gt; Don't wait for Google to discover you. Hit all four IndexNow endpoints.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structured data is not optional.&lt;/strong&gt; Review, FAQ, Dataset, Breadcrumb — use them all. And test with the GSC Rich Results tool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comparison pages rank fastest&lt;/strong&gt; for new sites. Long-tail, low competition, high conversion intent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor GSC daily.&lt;/strong&gt; Structured data errors, crawl issues, and indexing problems compound across thousands of pages. One bug = thousands of affected URLs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;33K impressions in 2 weeks is just the beginning.&lt;/strong&gt; The real game is turning impressions into clicks (better meta descriptions) and clicks into trust (time on site, return visits).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I'll post a month-3 update with traffic numbers once the sandbox period ends. If you're building something similar, I'm happy to answer questions in the comments.&lt;/p&gt;

&lt;p&gt;Built with Next.js, PostgreSQL, and Prisma. Live at &lt;a href="https://brokerrank.net/data/average-spreads" rel="noopener noreferrer"&gt;brokerrank.net/data/average-spreads&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>marketing</category>
      <category>nextjs</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How I Built a 10,000+ Page SEO Site with Next.js and PostgreSQL</title>
      <dc:creator>Christina Sanchez</dc:creator>
      <pubDate>Thu, 19 Mar 2026 11:29:16 +0000</pubDate>
      <link>https://dev.to/christina_sanchez_f16f40a/how-i-built-a-10000-page-seo-site-with-nextjs-and-postgresql-3ipp</link>
      <guid>https://dev.to/christina_sanchez_f16f40a/how-i-built-a-10000-page-seo-site-with-nextjs-and-postgresql-3ipp</guid>
      <description>&lt;p&gt;Most broker comparison sites are pay-to-play. Rankings depend on who pays the most, not actual quality. I built &lt;a href="https://brokerrank.net" rel="noopener noreferrer"&gt;BrokerRank&lt;/a&gt; to fix this — a transparent, data-driven comparison of 345+ trading brokers with 10,000+ pages.&lt;/p&gt;

&lt;p&gt;Here's how I did it technically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; (App Router) with TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; (Neon) + &lt;strong&gt;Prisma ORM&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TailwindCSS v4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; for hosting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISR&lt;/strong&gt; (Incremental Static Regeneration) for SEO&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Challenge: 10,000+ Unique Pages
&lt;/h2&gt;

&lt;p&gt;The site has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;345 broker review pages&lt;/li&gt;
&lt;li&gt;2,400+ broker sub-pages (fees, regulation, platforms, leverage, deposit, alternatives)&lt;/li&gt;
&lt;li&gt;6,500+ comparison pages (broker vs broker)&lt;/li&gt;
&lt;li&gt;550+ ranking pages (best forex brokers, best by country, etc.)&lt;/li&gt;
&lt;li&gt;300+ glossary terms&lt;/li&gt;
&lt;li&gt;250+ guides&lt;/li&gt;
&lt;li&gt;25 trading calculators with 60 pair sub-pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All generated from a single PostgreSQL database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programmatic SEO Architecture
&lt;/h2&gt;

&lt;p&gt;Every page type follows the same pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// app/broker/[slug]/page.tsx&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;revalidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt; &lt;span class="c1"&gt;// 24h ISR&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateStaticParams&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;brokers&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isActive&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;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;brokers&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;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="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;slug&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="nx"&gt;slug&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;At build time, Next.js pre-renders all pages. After 24 hours, ISR regenerates them with fresh data. This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast initial load (static HTML from CDN)&lt;/li&gt;
&lt;li&gt;Fresh content (updated daily)&lt;/li&gt;
&lt;li&gt;SEO-friendly (fully rendered HTML for crawlers)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Scoring Algorithm
&lt;/h2&gt;

&lt;p&gt;Each broker gets a weighted score (0-5):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regulation&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&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="n"&gt;fees&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.20&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="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.15&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="n"&gt;markets&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.10&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="n"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.15&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="n"&gt;ux&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regulation carries the most weight because for financial products, safety matters most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison Pages at Scale
&lt;/h2&gt;

&lt;p&gt;6,500+ comparison pages are generated from broker pairs. Instead of pre-rendering all at build time (which would timeout on Vercel), I removed &lt;code&gt;generateStaticParams&lt;/code&gt; for comparisons. They render on-demand with ISR caching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First visit: server renders and caches&lt;/li&gt;
&lt;li&gt;Subsequent visits: served from cache&lt;/li&gt;
&lt;li&gt;After 24h: regenerated with fresh data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SEO Results
&lt;/h2&gt;

&lt;p&gt;Every page includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema.org structured data (Review, FAQ, Breadcrumb, Article)&lt;/li&gt;
&lt;li&gt;Dynamic Open Graph images&lt;/li&gt;
&lt;li&gt;Proper canonical URLs&lt;/li&gt;
&lt;li&gt;Internal linking between related pages&lt;/li&gt;
&lt;li&gt;Sitemap with 10,600+ URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lighthouse scores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SEO: 100&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best Practices: 100&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance: 74&lt;/strong&gt; (Next.js runtime overhead)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LCP: 2.0s&lt;/strong&gt;, &lt;strong&gt;CLS: 0&lt;/strong&gt;, &lt;strong&gt;TBT: 70ms&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vercel has build limits&lt;/strong&gt; — 10K+ static pages can timeout. Use ISR for heavy routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jsdom doesn't work in Vercel serverless&lt;/strong&gt; — I had to replace
&lt;code&gt;isomorphic-dompurify&lt;/code&gt; with a lightweight regex sanitizer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma needs generate in build&lt;/strong&gt; — add &lt;code&gt;prisma generate &amp;amp;&amp;amp; next build&lt;/code&gt; to your build script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISR is powerful&lt;/strong&gt; — you get the best of static and dynamic. Pages are fast AND fresh.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Waiting for Google to index 10K+ pages (submitted sitemap to Google, Bing, Yandex)&lt;/li&gt;
&lt;li&gt;Building backlinks and community presence&lt;/li&gt;
&lt;li&gt;Adding real-time spread data from broker APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it out: &lt;a href="https://brokerrank.net" rel="noopener noreferrer"&gt;brokerrank.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the architecture, SEO strategy, or anything else!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>seo</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
