<?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: Ujwal Jayendran</title>
    <description>The latest articles on DEV Community by Ujwal Jayendran (@lactustech).</description>
    <link>https://dev.to/lactustech</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%2F3952999%2F789d729a-83d2-48fc-853a-75347576d14f.png</url>
      <title>DEV Community: Ujwal Jayendran</title>
      <link>https://dev.to/lactustech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lactustech"/>
    <language>en</language>
    <item>
      <title>I built a 200-page programmatic SEO site. Bing out-ranked Google 2.6X .</title>
      <dc:creator>Ujwal Jayendran</dc:creator>
      <pubDate>Fri, 19 Jun 2026 18:00:00 +0000</pubDate>
      <link>https://dev.to/lactustech/i-built-a-200-page-programmatic-seo-site-bing-out-ranked-google-26--544b</link>
      <guid>https://dev.to/lactustech/i-built-a-200-page-programmatic-seo-site-bing-out-ranked-google-26--544b</guid>
      <description>&lt;p&gt;Ten weeks ago I launched a niche reference site on a fresh &lt;code&gt;.in&lt;/code&gt; domain. No backlinks, no budget, no audience. Just a Next.js static export sitting on Cloudflare Pages.&lt;/p&gt;

&lt;p&gt;I expected the usual story: Google ignores you for months while you claw your way out of the sandbox.&lt;/p&gt;

&lt;p&gt;Instead I got a result I haven't seen written up anywhere — &lt;strong&gt;Bing started ranking me within days, and ten weeks in, it's sending me 2.6× more clicks than Google.&lt;/strong&gt; Not 2.6% more. Two-point-six times.&lt;/p&gt;

&lt;p&gt;Here's the data, the architecture, and the three lessons that surprised me.&lt;/p&gt;




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

&lt;p&gt;The site analyzes Indian life-insurance policies — specifically, it answers a question millions of policyholders have and can't easily answer: &lt;em&gt;should I keep this policy, surrender it, or stop paying premiums and let it sit?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The underlying data is public but published terribly. The insurer releases an annual actuarial valuation circular — a scanned PDF — declaring bonus rates per policy "group," not per plan. Mapping those rates to individual plans, structuring them, and building calculators on top is genuinely tedious work that nobody had done well.&lt;/p&gt;

&lt;p&gt;That's the whole programmatic-SEO thesis in one sentence: &lt;strong&gt;find public regulatory data trapped in a bad format, structure it, and build the queryable tool that should already exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;34 plan pages&lt;/strong&gt; (one per insurance plan)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;34 bonus-rate pages&lt;/strong&gt; (the data tables people actually search for)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7 calculators&lt;/strong&gt; (maturity value, surrender value, hold-vs-surrender analysis)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~100 explainer guides&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;200+ total URLs&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack: Next.js with &lt;code&gt;output: 'export'&lt;/code&gt; (fully static), Tailwind, deployed to Cloudflare Pages. Hosting cost: ₹0. Domain: about ₹1,000/year. That's the entire infrastructure bill.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture lesson: "add a plan = add a JSON entry"
&lt;/h2&gt;

&lt;p&gt;The trap with a site like this is writing per-plan logic. Plan A uses one bonus model, Plan B uses another, Plan C has survival payouts on a schedule that changes by term. If you hard-code each plan's math, you've signed up for 34 bespoke calculators and a maintenance nightmare.&lt;/p&gt;

&lt;p&gt;So I didn't. Every plan is a JSON entry with classification tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plan_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"165"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"classification"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bonus_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loyalty_addition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"premium_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"regular"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"benefit_structure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"endowment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sa_derived_from_premium"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"analyzer_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"loyalty_addition"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One universal calculator engine reads the tags and routes to the right modules:&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;function&lt;/span&gt; &lt;span class="nf"&gt;calculateBonus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yearsPaid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bonus_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;srb_fab&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="nf"&gt;srbFabBonus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yearsPaid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loyalty_addition&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="nf"&gt;loyaltyAddition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yearsPaid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guaranteed_addition&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="nf"&gt;guaranteedAddition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ga_plus_la&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="nf"&gt;gaPlusLoyalty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yearsPaid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;non_participating&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;total&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown bonus model: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bonus_model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a new plan means adding a JSON object. No new components, no new routes — &lt;code&gt;getStaticPaths&lt;/code&gt; generates the page, and the engine already knows how to compute everything from the tags.&lt;/p&gt;

&lt;p&gt;The hard part wasn't the code. It was discovering how many genuinely different calculation models exist behind a category that looks uniform from the outside. "Endowment plans" sounds like one thing. It's at least five, with different bonus structures, premium-payment models, and payout schedules. Modeling that taxonomy correctly was 80% of the work — and it's the part that's defensible, because nobody else bothered.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1: Bing has no sandbox (and rewards structured data immediately)
&lt;/h2&gt;

&lt;p&gt;Here's the week-over-week click data since launch:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Week&lt;/th&gt;
&lt;th&gt;Bing clicks&lt;/th&gt;
&lt;th&gt;Google clicks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;165&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;144&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;348&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;325&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bing went from 22 → 325 clicks/week in seven weeks. It started ranking my pages within days of indexing — no probation period. Google made me wait roughly three weeks before showing meaningful impressions, then rationed growth carefully.&lt;/p&gt;

&lt;p&gt;My read on &lt;em&gt;why&lt;/em&gt;: Bing's ranking appears to weight exact content-to-query relevance and structured data more heavily, and domain age / authority less heavily, than Google does. My pages are basically structured data tables answering very specific queries. That's Bing's sweet spot. Google, meanwhile, is appropriately skeptical of a brand-new domain spinning up 200 templated pages — that pattern also describes low-quality content farms, so it slow-walks you until behavioral signals prove otherwise.&lt;/p&gt;

&lt;p&gt;If you're launching programmatic content, &lt;strong&gt;submit to Bing Webmaster Tools on day one.&lt;/strong&gt; Most developers treat Bing as an afterthought. For this category of site, it may be your primary channel for the first few months — and it's a cleaner signal of whether your content is actually good, because it's not muddied by a months-long trust delay.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2: AI Overviews are quietly killing explainer content
&lt;/h2&gt;

&lt;p&gt;This is the finding I least expected, and it changed my content strategy completely.&lt;/p&gt;

&lt;p&gt;I split my pages into two buckets and looked at click-through rate by bucket:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data pages&lt;/strong&gt; (bonus-rate tables, calculators — specific numbers):&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;Position&lt;/th&gt;
&lt;th&gt;CTR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top bonus-rate table&lt;/td&gt;
&lt;td&gt;~7&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Bonus rates 2026" data page&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;8.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan calculator&lt;/td&gt;
&lt;td&gt;~10&lt;/td&gt;
&lt;td&gt;3.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Explainer pages&lt;/strong&gt; (how-to and conceptual guides):&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;Position&lt;/th&gt;
&lt;th&gt;CTR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Should I surrender my policy"&lt;/td&gt;
&lt;td&gt;~7&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"X vs Y, which is better"&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;0.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"How to revive a lapsed policy"&lt;/td&gt;
&lt;td&gt;~9&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same domain. Similar positions on page one. &lt;strong&gt;The explainer pages get effectively zero clicks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason is the AI Overview (and featured snippets before it). For a conceptual question — "should I surrender" — the search engine now synthesizes an answer at the top of the page. The user reads it and never scrolls to your result, even at position 6. Your ranking is worthless because the click never happens.&lt;/p&gt;

&lt;p&gt;But for "what is the exact bonus rate for plan 165 in 2026," the user needs a &lt;em&gt;specific figure for their specific situation&lt;/em&gt;. An AI summary saying "bonus rates vary by plan and term" doesn't satisfy that. They click through to get the actual number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The strategic takeaway:&lt;/strong&gt; in the AI-answer era, content that summarizes or explains is depreciating. Content that contains specific structured data people need to extract and act on is AI-resistant. I stopped writing explainer guides and doubled down on data pages and calculators. If you're planning a content site in 2026, weight your roadmap heavily toward "specific data the user needs for their case" and away from "here's how X works."&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3: the authority flywheel starts with one organic citation
&lt;/h2&gt;

&lt;p&gt;Around week ten, I noticed a referral from another reference site I'd never heard of. They'd independently built their own dataset page and cited mine as the source for a subset of figures the official circular doesn't publish — with a &lt;code&gt;dofollow&lt;/code&gt; link and an explicit note that their calculators consume my numbers.&lt;/p&gt;

&lt;p&gt;I didn't ask for it. Their team evaluated the available sources for that data and concluded mine was the one worth citing.&lt;/p&gt;

&lt;p&gt;That's the flywheel: &lt;strong&gt;good structured data → organic citations → domain authority → better rankings → more traffic → more citations.&lt;/strong&gt; The first quality backlink is the hardest to earn, and you can't really shortcut it with outreach for this kind of site — you earn it by having data nobody else compiled correctly.&lt;/p&gt;

&lt;p&gt;It also created a responsibility I hadn't considered: my numbers are now load-bearing for someone else's site too. If I have an error, it propagates. That pushed me to build an automated verification layer — feeding each page's content to an LLM acting as a domain expert to flag any figure that contradicts the source circular. Worth doing anyway; doubly worth it once other people depend on your data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The honest part: monetization has been a slog
&lt;/h2&gt;

&lt;p&gt;I'd be lying if I painted this as a clean win. The traffic curve is great. Turning it into money has been frustrating.&lt;/p&gt;

&lt;p&gt;I applied to the obvious ad network three times. Rejected three times — each time with the same generic "doesn't meet program policies" message. The most likely cause: a young &lt;code&gt;.in&lt;/code&gt; domain plus 200+ templated pages matches the heuristic for "scaled content," regardless of whether the content is actually good. New domains in this shape routinely need 3–6 months before approval.&lt;/p&gt;

&lt;p&gt;The mid-tier networks I'd have pivoted to have mostly raised their minimums recently — one popular option moved its floor to 250k monthly users this year. So the realistic path is: wait out the domain-age gate, keep the traffic compounding, and qualify for a premium network once I cross ~10k monthly sessions (which, at the current growth rate, lands right around the seasonal traffic spike).&lt;/p&gt;

&lt;p&gt;The lesson if you're building one of these: &lt;strong&gt;the monetization clock starts months after the traffic clock.&lt;/strong&gt; Plan for that gap. The flip side is that the cost structure is so low (₹1,000/year all-in) that you can let the asset compound for a long time without pressure. Most niche sites die because the builder quits during the monetization gap, not because the traffic fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways for anyone building programmatic SEO in 2026
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find public data trapped in a bad format.&lt;/strong&gt; Regulatory filings, government registers, actuarial circulars. The worse the official format, the bigger your opportunity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model the taxonomy before you write code.&lt;/strong&gt; The defensible work is discovering the real structure behind a category that looks uniform. "Add an entry = add a row" architecture follows from that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submit to Bing on day one.&lt;/strong&gt; It may carry you for the first few months while Google decides whether to trust you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build data pages, not explainer pages.&lt;/strong&gt; AI Overviews eat the latter. They can't replace the former.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect a monetization gap.&lt;/strong&gt; Traffic comes first by months. Keep costs near zero so you can wait it out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The site is &lt;a href="https://licpolicyadvisor.in/" rel="noopener noreferrer"&gt;licpolicyadvisor.in&lt;/a&gt; if you want to see the structure — it's part of a small portfolio of data-utility sites I build under NicheLabs. Happy to answer questions about the static-export setup, the classification engine, or the Bing-vs-Google divergence in the comments.&lt;/p&gt;

&lt;p&gt;If you've seen the same Bing-beats-Google pattern on a new domain, I'd love to know — I can't tell yet whether it's specific to structured-data sites or a more general new-site phenomenon.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>nextjs</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How I Built a Programmatic SEO Site with 16,750 Pages Using FastAPI and PostgreSQL</title>
      <dc:creator>Ujwal Jayendran</dc:creator>
      <pubDate>Tue, 26 May 2026 17:31:40 +0000</pubDate>
      <link>https://dev.to/lactustech/how-i-built-a-programmatic-seo-site-with-16750-pages-using-fastapi-and-postgresql-33ji</link>
      <guid>https://dev.to/lactustech/how-i-built-a-programmatic-seo-site-with-16750-pages-using-fastapi-and-postgresql-33ji</guid>
      <description>&lt;p&gt;&lt;em&gt;A deep dive into building BSBFinder.com — a free Australian BSB number lookup tool — from data pipeline to deployment.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every Australian bank transfer requires a BSB number — a 6-digit code identifying the bank and branch. There are over 16,750 active BSB codes, and the existing lookup tools were either clunky, ad-riddled, or buried inside bank websites.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://bsbfinder.com" rel="noopener noreferrer"&gt;BSBFinder.com&lt;/a&gt; — a fast, free tool that lets you search any BSB code and get the bank name, branch address, SWIFT code, and payment capabilities instantly.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI (Python)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templating:&lt;/strong&gt; Jinja2 (server-side rendered)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reverse Proxy:&lt;/strong&gt; Caddy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; Docker on AWS Lightsail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Source:&lt;/strong&gt; AusPayNet (official BSB registry)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose server-side rendering over a SPA framework deliberately. For a programmatic SEO site with 16,750+ individual pages, SSR gives you crawlable HTML that search engines can index immediately — no JavaScript rendering required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Pipeline
&lt;/h2&gt;

&lt;p&gt;The foundation of the entire project is the BSB dataset from AusPayNet, the official body that manages BSB allocations in Australia.&lt;/p&gt;

&lt;p&gt;The pipeline works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Extract&lt;/strong&gt; — Parse the official BSB data file (CSV format with bank codes, branch names, addresses, states, postcodes, and payment method flags)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enrich&lt;/strong&gt; — Add SWIFT/BIC codes by mapping BSB prefixes to their parent bank's international codes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot&lt;/strong&gt; — Store timestamped snapshots to track historical changes (branch closures, mergers, relocations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load&lt;/strong&gt; — Upsert into PostgreSQL with proper indexing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The snapshot system is one of the features I'm most proud of. Banks merge, branches close, and BSB codes get reassigned. By storing periodic snapshots, BSBFinder can show users when a BSB was last changed and what changed — useful for anyone dealing with an old BSB that no longer works.&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="c1"&gt;# Simplified snapshot comparison logic
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_changes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous_snapshot&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current_snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;previous_snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bsb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;added&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;previous_snapshot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bsb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;previous_snapshot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;previous_snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;current_snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bsb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bsb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discontinued&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Programmatic SEO: 16,750 Pages from Templates
&lt;/h2&gt;

&lt;p&gt;The core SEO strategy is programmatic — each BSB code gets its own page generated from a template. But "programmatic" doesn't mean "thin." Each BSB page includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The BSB number, bank name, and full branch address&lt;/li&gt;
&lt;li&gt;SWIFT/BIC code for the parent bank&lt;/li&gt;
&lt;li&gt;Payment method support (BECS, NPP, Direct Entry)&lt;/li&gt;
&lt;li&gt;Nearby branches from the same bank&lt;/li&gt;
&lt;li&gt;A FAQ section with the most common questions for that specific BSB&lt;/li&gt;
&lt;li&gt;Schema.org structured data for rich search results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key lesson I learned: &lt;strong&gt;Google treats 16,750 near-identical pages very differently from 16,750 pages that each have unique, useful content.&lt;/strong&gt; The nearby branches section, the dynamic FAQ, and the payment capability details make each page genuinely different.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Structure
&lt;/h3&gt;

&lt;p&gt;I went with a flat, readable structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/bsb/062-000    → Individual BSB page
/bank/cba       → All BSBs for Commonwealth Bank
/state/nsw      → All BSBs in New South Wales
/suburb/vic/melbourne → BSBs in Melbourne, VIC
/postcode/2000  → BSBs in postcode 2000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates natural internal linking — each BSB page links to its bank page, state page, suburb page, and postcode page, creating a deep web of interconnections that search engines love.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sitemaps at Scale
&lt;/h2&gt;

&lt;p&gt;With 16,750 BSB pages plus bank pages, state pages, suburb pages, postcode pages, and guide articles, the total URL count exceeds 20,000. Google's sitemap limit is 50,000 URLs per file, but I split them into batches of 5,000 for better crawl management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/sitemap-index.xml
  ├── /sitemap-bsb-1.xml     (5,000 BSB pages)
  ├── /sitemap-bsb-2.xml     (5,000 BSB pages)
  ├── /sitemap-bsb-3.xml     (5,000 BSB pages)
  ├── /sitemap-bsb-4.xml     (remaining BSB pages)
  ├── /sitemap-banks.xml      (bank pages)
  ├── /sitemap-locations.xml  (state, suburb, postcode pages)
  └── /sitemap-guides.xml     (editorial content)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I submitted these in batches over several days rather than all at once — it helps avoid overwhelming the crawler and triggering spam flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;p&gt;Beyond the core lookup, I built several tools that add genuine utility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BSB Validator&lt;/strong&gt; — Verify a BSB number is valid before making a transfer (checks format, prefix validity, and whether the BSB is still active)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk Lookup&lt;/strong&gt; — Upload a CSV of BSB numbers and get all details back in one go (useful for payroll and accounting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BSB Decoder&lt;/strong&gt; — Break down what each digit means (first 2 = bank, next 1 = state, last 3 = branch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch Compare&lt;/strong&gt; — Side-by-side comparison of two branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SWIFT Lookup&lt;/strong&gt; — Find the SWIFT/BIC code for any Australian bank&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tool has its own page with its own SEO value. The validator alone generates meaningful search traffic for queries like "check BSB number" and "BSB validator."&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;For a site that serves 16,750+ unique pages, performance matters. Here's what I optimized:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database queries:&lt;/strong&gt; Every BSB lookup is a simple primary key query — O(1) with PostgreSQL's B-tree index. Response times are under 5ms for any BSB lookup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caddy as reverse proxy:&lt;/strong&gt; Caddy handles TLS termination, HTTP/2, and automatic HTTPS. It also serves static assets directly without hitting the FastAPI backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker deployment:&lt;/strong&gt; The entire stack runs in Docker containers on a single AWS Lightsail instance ($5/month). For the traffic levels of a niche utility site, this is more than sufficient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (simplified)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Free REST API
&lt;/h2&gt;

&lt;p&gt;I also built a free REST API for developers who need BSB data programmatically. It supports single lookups, search, and bank/branch listing — all without authentication for reasonable usage.&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;# Look up a BSB&lt;/span&gt;
curl https://bsbfinder.com/api/bsb/062-000

&lt;span class="c"&gt;# Search by bank name&lt;/span&gt;
curl https://bsbfinder.com/api/search?q&lt;span class="o"&gt;=&lt;/span&gt;commonwealth+sydney
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This serves a dual purpose: it's genuinely useful for developers building Australian fintech products, and it creates an incentive for technical blogs and documentation to link to BSBFinder as a data source.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;1. Server-side rendering wins for programmatic SEO.&lt;/strong&gt; SPA frameworks add unnecessary complexity when your primary goal is search engine indexing. Jinja2 templates are simple, fast, and produce crawlable HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Content depth matters more than content volume.&lt;/strong&gt; 16,750 thin pages will get flagged. 16,750 pages that each answer a specific question with unique data will get indexed and ranked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sitemaps are a conversation with search engines.&lt;/strong&gt; Don't dump 20,000 URLs at once. Submit in batches, monitor indexing rates, and expand as trust builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Internal linking is your most powerful on-page SEO tool.&lt;/strong&gt; Every BSB page links to its bank, state, suburb, and postcode pages. Every bank page links to all its branches. This creates a dense link graph that distributes authority across the entire site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Build tools, not just pages.&lt;/strong&gt; The validator, bulk lookup, and decoder tools generate their own search traffic and give users a reason to bookmark the site.&lt;/p&gt;

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

&lt;p&gt;I'm tracking historical BSB changes to build a "discontinued BSBs" section — useful for anyone trying to figure out where an old BSB number was redirected. I'm also exploring a premium API tier for high-volume users.&lt;/p&gt;

&lt;p&gt;If you're building a programmatic SEO site, the key takeaway is this: &lt;strong&gt;every page should answer a question that someone is actually searching for.&lt;/strong&gt; If it doesn't, it's filler — and search engines can tell.&lt;/p&gt;




&lt;p&gt;Check out &lt;a href="https://bsbfinder.com" rel="noopener noreferrer"&gt;BSBFinder.com&lt;/a&gt; if you're curious, or hit the &lt;a href="https://bsbfinder.com/api" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; if you want to build something with it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions? Drop a comment below — happy to dive deeper into any part of the stack.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>seo</category>
      <category>fastapi</category>
    </item>
  </channel>
</rss>
