<?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: Tim</title>
    <description>The latest articles on DEV Community by Tim (@yx-studio).</description>
    <link>https://dev.to/yx-studio</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%2F4051771%2F565faaa6-8f72-4a1c-af7a-5f29e3229b7b.png</url>
      <title>DEV Community: Tim</title>
      <link>https://dev.to/yx-studio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yx-studio"/>
    <language>en</language>
    <item>
      <title>How I tried five affiliate networks in one afternoon — and what the geo-friction taught me</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:00:23 +0000</pubDate>
      <link>https://dev.to/yx-studio/how-i-tried-five-affiliate-networks-in-one-afternoon-and-what-the-geo-friction-taught-me-113l</link>
      <guid>https://dev.to/yx-studio/how-i-tried-five-affiliate-networks-in-one-afternoon-and-what-the-geo-friction-taught-me-113l</guid>
      <description>&lt;p&gt;I run a small affiliate site from China. Five programs in one Saturday afternoon: one working, one declined, one emailed, two dropped.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://proteinpayment.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;proteinpayment.com&lt;/strong&gt;&lt;/a&gt; from China. It is a Hugo static-export site with 50+ articles about paying for whey, mass gainer, and protein boxes across India, Indonesia, Brazil, and Mexico. Cloudflare Pages hosts it, D1 backs the newsletter form, Resend sends the welcome email. The boring plumbing part of an Amazon affiliate site.&lt;/p&gt;

&lt;p&gt;I spent Saturday afternoon applying to four more affiliate programs — LazyPay, Simpl, MyProtein, HealthKart — plus Impact.com, which I already had an account on. Here is what actually happened, all five in one day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon Associates: working since 2024
&lt;/h2&gt;

&lt;p&gt;Nothing to report here. The boring fact is: this is the only program I have been paid through, and the only one that did not ask me where I live. &lt;a href="https://affiliate-program.amazon.com/" rel="noopener noreferrer"&gt;Amazon Associates&lt;/a&gt; does not care if you are in Bangalore or Beijing. That alone made it the longest survivor in my stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact.com: marketplace declined, account still active
&lt;/h2&gt;

&lt;p&gt;I applied to Impact.com to reach MyProtein and HealthKart via their catalog. Created the publisher account, accepted the Master Publisher Service Agreement, walked through every profile question. Two hours later I got a "marketplace application declined" notice.&lt;/p&gt;

&lt;p&gt;The second line of that notice is the interesting one: "This does not affect any of your existing or pending brand program relationships. Your impact.com account can continue to be used when partnering directly with brands via their unique sign-up links and when accepting invitations to brand programs."&lt;/p&gt;

&lt;p&gt;A polite way of saying: marketplace auto-reject is per-brand; the account itself is fine.&lt;/p&gt;

&lt;p&gt;So I read the &lt;a href="https://app.impact.com/content/displaympserviceagreement.ihtml" rel="noopener noreferrer"&gt;Master Publisher Service Agreement&lt;/a&gt; at app.impact.com. Zero hits for "China", "Chinese", "OFAC", or "sanctioned" anywhere in the agreement. The block is per-brand, not contractual. Section 3.1 mentions an Account Management Fees path applied "when Impact.com is unable to verify the information and/or documents submitted by Partner" — which matches a non-US bank / non-SSN KYC failure cleanly.&lt;/p&gt;

&lt;p&gt;So Impact is not the destination. It is a tool for direct brand invites, which I have not yet triggered.&lt;/p&gt;

&lt;h2&gt;
  
  
  LazyPay: applied by email, awaiting response
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.lazypay.in/" rel="noopener noreferrer"&gt;LazyPay&lt;/a&gt;'s footer shows "Partners" — but it is merchant partners, food and e-commerce brands. Clicking through gives a 403 on every URL variant I tried, including with a VPN node in Mumbai.&lt;/p&gt;

&lt;p&gt;There is no public affiliate signup page. The path is email outreach only.&lt;/p&gt;

&lt;p&gt;I sent to &lt;a href="mailto:wecare@lazypay.in"&gt;&lt;code&gt;wecare@lazypay.in&lt;/code&gt;&lt;/a&gt;. Sent from my personal Gmail because the domain email &lt;code&gt;yanxi002studio@proteinpayment.com&lt;/code&gt; is set up as receive-only via Cloudflare Email Routing — it has no SMTP, so I cannot send "as" it. Reply-To was not set; the lazy fallback was personal Gmail.&lt;/p&gt;

&lt;p&gt;If LazyPay replies with a KYC path that needs an Indian phone, that will be the next place I find out whether India-resident KYC has any China alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simpl: dropped
&lt;/h2&gt;

&lt;p&gt;The real "what" of this one: &lt;code&gt;getsimpl.com&lt;/code&gt; is a Namecheap parking page. Not the company. Their main domain appears to be &lt;a href="https://simpl.com" rel="noopener noreferrer"&gt;simpl.com&lt;/a&gt; (and / or &lt;a href="https://getsimpl.in" rel="noopener noreferrer"&gt;getsimpl.in&lt;/a&gt; for the Indian variant), but none of those URLs led to an affiliate signup when I tried them either. Dropped from P2.&lt;/p&gt;

&lt;p&gt;The lesson: when a domain returns a hosted-by-Namecheap placeholder, do not dig deeper. Walk away.&lt;/p&gt;

&lt;h2&gt;
  
  
  MyProtein: on hold
&lt;/h2&gt;

&lt;p&gt;Was the original reason I signed up for Impact.com. The catalog refused my marketplace application, so the workaround path is direct outreach to &lt;a href="mailto:affiliates@myprotein.com"&gt;&lt;code&gt;affiliates@myprotein.com&lt;/code&gt;&lt;/a&gt; asking them to issue an Impact partnership from their side.&lt;/p&gt;

&lt;p&gt;I decided not to send that email today. Marketplace auto-reject is a routine outcome, not a hard fail; waiting to see what LazyPay comes back with is a better use of attention than firing off a parallel email that probably will not matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  HealthKart: dropped
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.healthkart.com/" rel="noopener noreferrer"&gt;HealthKart&lt;/a&gt; does have an affiliate program. The "Refer &amp;amp; Earn" link in their footer takes you to a login / signup page. The signup page asks for a 10-digit mobile number with a &lt;code&gt;+91&lt;/code&gt; country code and an OTP. There is no fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I learned in one afternoon
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One. Indian BNPL and supplement affiliate programs have systemic China-resident friction.&lt;/strong&gt; Of the four non-Amazon options I tried, three either required an Indian phone, parked their brand domain, or operated auto-reject for China-resident publisher accounts through brand-side geo rules. Only LazyPay gave me a real email-able contact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two. Service agreements tell you more than rejection emails do.&lt;/strong&gt; A 30-second grep of the MPSA saved me an afternoon of speculative work. Reading terms is unfashionable; reading them when you are blocked is the highest-leverage thing you can do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three. Document dead ends as well as successes.&lt;/strong&gt; I updated &lt;a href="https://github.com/TZ005/protein-payment/blob/main/AFFILIATE-APPLICATION-GUIDE.md" rel="noopener noreferrer"&gt;&lt;code&gt;AFFILIATE-APPLICATION-GUIDE.md&lt;/code&gt;&lt;/a&gt; to record everything I learned today: the actual signup URL variants I tested, the actual domain that was a placeholder, the actual signup page that wanted an Indian number. The next time I open that file, those four hours of trial and error are not lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;I will hear back from LazyPay in one to four weeks, if at all. If they come back with a real path I will run it; if not, I will add two words to this post — "and got nothing" — and call P2 a structural dead end rather than keep grinding.&lt;/p&gt;

&lt;p&gt;The thing I keep coming back to is that LazyPay / Simpl / HealthKart are not the business. Amazon Associates is. The reason to keep trying the others is upside, not foundation. Knowing where to stop trying is, in a small way, also part of building.&lt;/p&gt;

&lt;p&gt;You can ship a similar small affiliate site from anywhere in the world if Amazon is your monetization. Where you live starts to matter the moment you try to add a second program.&lt;/p&gt;

&lt;p&gt;Tim Yx runs &lt;a href="https://proteinpayment.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;proteinpayment.com&lt;/strong&gt;&lt;/a&gt;, an English-language affiliate site covering BNPL, EMI, and other payment methods for protein supplement buyers across India, Indonesia, Brazil, and Mexico.&lt;/p&gt;

</description>
      <category>affiliate</category>
      <category>seo</category>
      <category>china</category>
      <category>indie</category>
    </item>
    <item>
      <title>How I shipped a free Next.js calculator with Amazon affiliate links — and what 180 days of tracking taught me</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Mon, 03 Aug 2026 07:50:22 +0000</pubDate>
      <link>https://dev.to/yx-studio/how-i-shipped-a-free-nextjs-calculator-with-amazon-affiliate-links-and-what-180-days-of-tracking-3noe</link>
      <guid>https://dev.to/yx-studio/how-i-shipped-a-free-nextjs-calculator-with-amazon-affiliate-links-and-what-180-days-of-tracking-3noe</guid>
      <description>&lt;p&gt;Twelve days into a free sourdough hydration calculator monetized through Amazon Associates, the data shows 0 clicks and an average position of 43.3. Here is what is actually working, what I should have done on day one, and what surprised me most about Cloudflare's defaults in 2026.&lt;/p&gt;

&lt;p&gt;Twelve days ago, I shipped a free tool at &lt;a href="https://sourdough-hydrationcalculator.com/" rel="noopener noreferrer"&gt;sourdough-hydrationcalculator.com&lt;/a&gt;. The plan in theory was simple: a useful calculator for home bakers, monetized through Amazon affiliate links. No signup, no database, no server.&lt;/p&gt;

&lt;p&gt;What I did not plan for was the clock. Amazon Associates closes new accounts if they do not hit three qualifying sales within 180 days of sign-up. So somewhere between curiosity and a deploy script, my hobby project became a small business with a deadline.&lt;/p&gt;

&lt;p&gt;This is not a "I made money" post. The honest number is zero sales so far. What I can share is everything I learned about building a Next.js calculator, wiring affiliate links correctly, and instrumenting the early data so I can see what is actually working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Next.js choice
&lt;/h2&gt;

&lt;p&gt;The calculator does exactly one thing: take a flour weight and return water, salt, and starter amounts. It also shows a hydration chart from 50% to 100%.&lt;/p&gt;

&lt;p&gt;I considered three stacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A React SPA with a backend&lt;/li&gt;
&lt;li&gt;Hugo or Astro, content-first&lt;/li&gt;
&lt;li&gt;Next.js with static export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static export won because the SEO value of content lives with the marketing, and content marketing for niche calculators is mostly blog posts, FAQs, and comparison tables. Static export lets every blog post be server-rendered HTML, which is what Google prefers and what AI search tools can read reliably.&lt;/p&gt;

&lt;p&gt;The build is &lt;code&gt;next build&lt;/code&gt; with &lt;code&gt;output: export&lt;/code&gt;, deployed to Cloudflare Pages on every commit to &lt;code&gt;main&lt;/code&gt;. There is no server, no runtime cost, no auth flow to maintain. That choice reflects a bet: most of my real work over the next 180 days will not be writing code, it will be writing articles and watching what ranks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 180-day Amazon clock
&lt;/h2&gt;

&lt;p&gt;This is the part I did not appreciate when I started.&lt;/p&gt;

&lt;p&gt;Amazon's Associates Terms require three qualifying sales within 180 days of sign-up. If you do not hit that threshold, the account is closed. There is no extension, and re-applying is harder than the first time.&lt;/p&gt;

&lt;p&gt;Two practical implications.&lt;/p&gt;

&lt;p&gt;First, the clock starts the moment you sign up, not when traffic arrives. If you spend 90 days building before launching, you have 90 days of runway left, not 180.&lt;/p&gt;

&lt;p&gt;Second, the model is not "monetize after you have an audience." It is "monetize from page one." Your first six months are paid existence, with traffic as the cost of admission.&lt;/p&gt;

&lt;p&gt;If you want optionality, you can build the site without affiliate links for the first month, do real SEO work, then add Amazon once you have a page-one ranking to point at. That gives you the freedom to launch earlier.&lt;/p&gt;

&lt;p&gt;I shipped the calculator with affiliate links already in place. I would not do that again.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I structured affiliate links
&lt;/h2&gt;

&lt;p&gt;Because Amazon is strict about disclosure and Google penalizes schemes, I did four things that I think are non-negotiable.&lt;/p&gt;

&lt;p&gt;First, a single disclosure prominently visible on every page that has an affiliate link. The exact sentence, "As an Amazon Associate, we earn from qualifying purchases," is required and cannot be edited. Add a small note that lowers anxiety, such as "Prices stay the same for you." Most readers who see Amazon links wonder whether they will pay more if they use yours. They will not. That sentence is worth more than it sounds.&lt;/p&gt;

&lt;p&gt;Second, every Amazon link carries &lt;code&gt;rel="sponsored nofollow noopener noreferrer"&lt;/code&gt;. The &lt;code&gt;sponsored&lt;/code&gt; value is the Google-required attribute for paid placements. &lt;code&gt;nofollow&lt;/code&gt; keeps link equity inside the site. &lt;code&gt;noopener noreferrer&lt;/code&gt; is basic security.&lt;/p&gt;

&lt;p&gt;Third, no more than two affiliate links per blog post. A page that recommends six kitchen tools reads like a shopping list. A page that recommends the two tools relevant to the article reads like advice. Google's "thin affiliate" filter targets the first pattern, and so do readers.&lt;/p&gt;

&lt;p&gt;Fourth, links go only on pages where the product genuinely fits the article. Recipes and explainers that never benefited from a product do not get a link.&lt;/p&gt;

&lt;h2&gt;
  
  
  What twelve days of data actually shows
&lt;/h2&gt;

&lt;p&gt;From Google Search Console, twelve days after launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 pages are tracked&lt;/li&gt;
&lt;li&gt;10 are indexed&lt;/li&gt;
&lt;li&gt;14 are not yet indexed&lt;/li&gt;
&lt;li&gt;Average position across all impressions: 43.3&lt;/li&gt;
&lt;li&gt;Clicks: 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 0 clicks and 43.3 average are not failures. They mean the site is on page four or five of Google for whatever queries it ranks. No human clicks through page four.&lt;/p&gt;

&lt;p&gt;The reason for the slow start turned out to be infrastructure, not content. Cloudflare's Bot Fight Mode was quietly serving Googlebot an HTML 403 page where browsers saw a sitemap. One toggle in Cloudflare's Security → Bots fixed it.&lt;/p&gt;

&lt;p&gt;This is the part I want to flag for anyone shipping a free site in 2026. Cloudflare's default protections now actively block verified search engine crawlers, even ones that have been verified for years. If your new site has 0 clicks and a low impression count, check this before you spend hours on content audits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I would do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Add &lt;code&gt;llms.txt&lt;/code&gt; on day one, not day thirty.&lt;/strong&gt; ChatGPT, Perplexity, and Google's AI Overviews crawl the web looking for citation candidates. &lt;code&gt;llms.txt&lt;/code&gt; is a small plain-text file at the root that says, "Here is what this site is, here are its most citable pages." It costs about twenty minutes to write. If you have any hope of being cited by an AI answer in the next six months, the file is a cheap edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use GSC's URL Inspection to request indexing, rather than wait for the sitemap.&lt;/strong&gt; Google Search Console has a "Request Indexing" button when you paste a URL into the URL Inspection tool. It is faster than waiting for the sitemap to be processed, which can take one to three days. There is a daily limit of about ten requests per site, so for a new site with thirty pages, request the five most important pages on day one, then the next five the next day, and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not change settings you do not understand.&lt;/strong&gt; This one is mostly a self-note. I once changed emoji icons to text badges because the emoji looked broken in some browsers. The user (me, in this case) wanted emoji back. The change wasted two deploys and added no value. The lesson for shipping early is to leave visuals alone once they work. Optimize when there is a real signal that they need to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The next four weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request indexing for the six articles that were crawled on the old version of the site&lt;/li&gt;
&lt;li&gt;Wait for Google to re-evaluate with the new schema, FAQ, and citation changes in place&lt;/li&gt;
&lt;li&gt;Publish five to ten long-tail articles that match specific home-baking queries&lt;/li&gt;
&lt;li&gt;Measure clicks, not impressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the calculator ever does its job, the result will be a small number of Amazon sales, a steady stream of home bakers finding the hydration ratio they actually need, and a real-time lesson on what AI-era SEO looks like in 2026.&lt;/p&gt;

&lt;p&gt;For now, the project is in its quiet phase. The tool is at &lt;a href="https://sourdough-hydrationcalculator.com/" rel="noopener noreferrer"&gt;sourdough-hydrationcalculator.com&lt;/a&gt;, and the only thing I am working on is making sure Google can see it.&lt;/p&gt;

&lt;p&gt;If you're shipping a similar small site, the early moves that mattered most were the boring ones: real schema on real pages, real citations on real articles, a sitemap that does not lie. The flashy stuff is optional. The plumbing is not.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>seo</category>
      <category>amazon</category>
      <category>indie</category>
    </item>
    <item>
      <title>What an SEO Audit Taught Me About Shipping a Next.js Static Site</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Sat, 01 Aug 2026 13:26:42 +0000</pubDate>
      <link>https://dev.to/yx-studio/what-an-seo-audit-taught-me-about-shipping-a-nextjs-static-site-4fp7</link>
      <guid>https://dev.to/yx-studio/what-an-seo-audit-taught-me-about-shipping-a-nextjs-static-site-4fp7</guid>
      <description>&lt;p&gt;I ran an SEO audit on my Next.js 14 static site and found 20+ issues. Here is exactly what I fixed, what broke, and what actually mattered.&lt;/p&gt;

&lt;p&gt;Last month I shipped a free tool called &lt;a href="https://sourdough-hydrationcalculator.com/" rel="noopener noreferrer"&gt;Sourdough Hydration Calculator&lt;/a&gt;. It is a Next.js 14 static export hosted on Cloudflare Pages. No database, no signup, no server. Just a calculator, a chart, and a bunch of blog posts.&lt;/p&gt;

&lt;p&gt;Then I ran an SEO audit on it. The score was not terrible, but the report was full of small warnings: missing favicon, descriptions too long, keywords too long, no canonical on blog listing pages, no author markup, headings that jumped levels, and a few citations that did not exist yet.&lt;/p&gt;

&lt;p&gt;None of these issues were going to kill the site. Together, though, they made the whole thing feel unfinished. So I fixed them one by one. This post is what I learned, including the mistakes I made along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the boring metadata
&lt;/h2&gt;

&lt;p&gt;The first round of fixes was pure housekeeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Meta descriptions under 160 characters&lt;/li&gt;
&lt;li&gt;Titles under 60 characters&lt;/li&gt;
&lt;li&gt;Keywords meta under 100 characters (yes, Google ignores it, but Bing still reads it)&lt;/li&gt;
&lt;li&gt;A real favicon in SVG, 32px PNG, and 180px PNG&lt;/li&gt;
&lt;li&gt;Canonical tags on every page, including paginated blog pages&lt;/li&gt;
&lt;li&gt;Open Graph and Twitter cards with a consistent site name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern that worked for every blog post was simple:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
export const metadata = {
  title: "Sourdough Bulk Fermentation: Timing and Temperature",
  description: "When to end sourdough bulk fermentation: 5 visual cues, temperature timing, and common mistakes.",
  alternates: {
    canonical: "https://sourdough-hydrationcalculator.com/blog/bulk-fermentation/",
  },
  openGraph: {
    siteName: "SourdoughCalc",
    type: "article",
    url: "https://sourdough-hydrationcalculator.com/blog/bulk-fermentation/",
  },
};
Nothing clever. The audit score moved from warnings to clean because the page finally said the same thing everywhere: this is one page, this is its name, this is what it is about.
Remove fake trust signals
My first version of the homepage included structured data with a rating:

{
  "@type": "SoftwareApplication",
  "name": "SourdoughCalc",
  "aggregateRating": {
    "ratingValue": "4.8",
    "ratingCount": "127"
  }
}

I had no real reviews behind those numbers. That is the kind of thing Google can penalize as deceptive, and it is not worth the risk. I deleted the aggregate rating and kept the rest of the schema. If we ever collect real reviews, we can add them back honestly.
Broken images hide in plain sight
The audit did not find this, but a simple script did: 19 blog posts referenced PNG hero images that did not exist. The &amp;lt;picture&amp;gt; element pointed to a WebP source, so modern browsers never requested the PNG. The page looked fine.
The problem was structured data. Article schema pointed to the missing PNG, which meant search engines and AI crawlers were told to look at an image that returned 404.
The fix was mechanical:

image: "/images/blog/bulk-fermentation.webp"

Lesson: test the URLs that only machines read, not just the ones humans see.
My sitemap broke in a boring way
I wanted to update every &amp;lt;lastmod&amp;gt; in the sitemap to today's date. I wrote a regex replacement in PowerShell and pushed it without looking at the output carefully enough.
The replacement produced this:

&amp;lt;loc&amp;gt;https://sourdough-hydrationcalculator.com/blog/...&amp;lt;/loc&amp;gt;
$12026-08-01&amp;lt;/lastmod&amp;gt;

Every single lastmod lost its opening tag and gained a literal $1. The file was still deployed. Nothing crashed. It just became invalid, quietly.
The lesson is obvious in hindsight: after any batch edit, parse the file, count the records, and print a sample. I now validate the sitemap with an XML parser before every deploy.
Structured data is not just for articles
Blog posts already had Article schema. The blog listing page had none. So I added a Blog entity with author, publisher, dateModified, and a list of the posts on that page:

{
  "@context": "https://schema.org",
  "@type": "Blog",
  "name": "Sourdough Baking Blog",
  "url": "https://sourdough-hydrationcalculator.com/blog/",
  "author": {
    "@type": "Organization",
    "name": "SourdoughCalc",
    "url": "https://sourdough-hydrationcalculator.com/"
  },
  "dateModified": "2026-08-01"
}

The same page also needed a byline. Anonymous content is cited less, and the fix was one line: By SourdoughCalc Team. We did not invent a fake person; an honest team attribution is enough.
Headings should not jump levels
The audit complained about a heading jump from H2 to H4. The culprit was the footer: it used &amp;lt;h4&amp;gt; for column titles. On article pages that was fine, but on listing pages with only H1 and H2, the footer created a skipped level.
Changing four footer headings from h4 to h3 fixed the whole site:

&amp;lt;h3 class="text-brand-wheat mb-3"&amp;gt;Quick Links&amp;lt;/h3&amp;gt;

Small change, site-wide effect.
Citations helped more than I expected
The audit kept saying "No citations or quotations". I did not want to link to competitors at the top of articles, so I added a small Sources section at the bottom of the posts that made scientific or technical claims.
For nutrition and digestion posts, I used real PubMed studies published in 2026. For baking technique posts, I linked to established sources like King Arthur Baking and The Perfect Loaf.
Two rules:
Verify every URL returns 200 before adding it
Add rel="nofollow noopener" so citations do not leak link equity
The result: the citations are visible to readers and AI tools, but Google does not treat them as endorsements.
Headings that read like questions get cited more
This one is not about rankings, it is about AI visibility. Sections titled What Temperature Should You Bake Sourdough? are easier for AI answers to pull from than sections titled Bake Day (Evening).
I reframed key H2s across the blog:
Why Is Focaccia Perfect for Beginners?
How Do You Fix Sourdough Pizza Problems?
Does Hydration Affect the Crust?
It is a small content change, but it makes every post more extractable.
Add a file for AI crawlers
robots.txt tells search engines what they may crawl. llms.txt is a younger idea: a plain text file that tells AI tools what your site is and which pages matter.

# SourdoughCalc

&amp;gt; Free sourdough hydration calculator for home bakers.

https://sourdough-hydrationcalculator.com/
https://sourdough-hydrationcalculator.com/blog/

Adoption is still low, but the file costs nothing and gives AI crawlers a clean starting point.
Verify the deployed page, not just the build
My biggest mistake was trusting my own verification too early.
After one deploy, I fetched the homepage and wrote a quick regex to check the affiliate links. The regex returned the URL but not the anchor tag around it, so I concluded that the live site was missing rel attributes and that the deployment was stale.
It was not. The site had been updated; my check was wrong.
Now I wait 15 seconds after a deployment reaches success, fetch the real HTML, and compare exact strings before reporting anything.
What actually mattered
If I ranked the fixes by impact:
1.Fixing broken image URLs in structured data
2.Removing fake aggregate ratings
3.Adding canonical tags everywhere
4.Making the sitemap valid again
5.Adding honest author and Blog schema
6.Adding real, nofollow citations
7.Reframing headings as questions
None of these were dramatic. Most were boring. Together they turned a site that looked finished into one that was actually finished.
The tool itself is still free and still does one thing: Sourdough Hydration Calculator. If you are shipping a static site, I hope this list saves you a few audits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>nestjs</category>
      <category>seo</category>
      <category>static</category>
      <category>cloudflarepages</category>
    </item>
    <item>
      <title>How I Built a Sourdough Hydration Calculator with Next.js 14 (Free Tool)</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Tue, 28 Jul 2026 17:10:48 +0000</pubDate>
      <link>https://dev.to/yx-studio/how-i-built-a-sourdough-hydration-calculator-with-nextjs-14-free-tool-pg9</link>
      <guid>https://dev.to/yx-studio/how-i-built-a-sourdough-hydration-calculator-with-nextjs-14-free-tool-pg9</guid>
      <description>&lt;p&gt;Why I built it&lt;/p&gt;

&lt;p&gt;Every sourdough recipe online gives you hydration as a percentage, but most home bakers have no idea what that means in practice. You see "75% hydration" and think "OK, 75% of what?" I watched my partner struggle with this for months before I finally wrote a calculator that shows the actual grams of water, flour, and starter to weigh out.&lt;/p&gt;

&lt;p&gt;The result is a free, no-signup tool at &lt;a href="https://sourdough-hydrationcalculator.com/" rel="noopener noreferrer"&gt;Sourdough Hydration Calculator&lt;/a&gt;. This post walks through the math, the tech stack, and a few decisions that made the project work on a $0/month budget.&lt;/p&gt;

&lt;p&gt;The math: baker's percentages explained&lt;/p&gt;

&lt;p&gt;Baker's percentages are simple once you see them as ratios. Every ingredient is expressed as a percentage of the total flour weight:&lt;/p&gt;

&lt;p&gt;Hydration % = (water weight) / (flour weight) * 100&lt;/p&gt;

&lt;p&gt;So a 75% hydration dough has 75g of water for every 100g of flour. Easy.&lt;/p&gt;

&lt;p&gt;The wrinkle is that sourdough starter is 50% flour and 50% water. A 100g portion of starter contributes 50g of each to your dough. The calculator handles this automatically:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
const water = flour * (hydration / 100);
const saltGrams = flour * (salt / 100);
const totalStarter = flour * (starter / 100);
const starterFlour = totalStarter * 0.5;
const starterWater = totalStarter * 0.5;
const flourToAdd = flour - starterFlour;
const waterToAdd = water - starterWater;
const totalDough = flourToAdd + waterToAdd + saltGrams + totalStarter;

The output breaks out each ingredient in grams, plus a total dough weight. Six preset hydration levels (65% bagels through 100% ciabatta) handle the common cases.

Tech stack
Next.js 14 (App Router, static export)
TypeScript (strict mode)
Tailwind CSS 3.4
Cloudflare Pages (free hosting)
Plausible Analytics (privacy-friendly, no cookies)
Google Search Console + Bing Webmaster Tools (verification + indexing)
Total monthly cost: $0.
Architecture highlights
Client-side only. No backend, no database, no API. The whole site is a static export. This keeps the bundle small (87 KB shared JS, 19 KB CSS) and the hosting free.
Optimized for Core Web Vitals. Every blog post is pre-rendered at build time. The biggest image (the 1200x630 hero) is converted to WebP at 122 KB. Inline critical CSS via Tailwind.
SEO from day one. Every page has:
Sitemap entry in sitemap.xml
JSON-LD Article schema (auto-generated via shared component)
Open Graph + Twitter Card meta tags
Canonical URL pointing to itself
Internal links between related posts
Two output modes. Grams for serious bakers, plus a "cups + tsp" toggle for casual bakers. Salt is shown in teaspoons at small quantities because 10g of salt as "0.04 cups" is useless in practice.
Why Next.js 14 specifically
I started with vanilla React + Vite, but Next.js's static export (output: 'export') gave me:
File-based routing (no React Router setup)
TypeScript + ESLint out of the box
Built-in CSS modules + Tailwind integration
Image optimization (even for static export)
One-command deploy to Cloudflare Pages
The catch: next/image doesn't work with output: 'export' by default. Setting images.unoptimized: true disables it. The trade-off is acceptable for a small site.

What's free, what's not

| Component | Provider | Cost |
|---|---|---|
| Hosting | Cloudflare Pages | $0 |
| Domain | Cloudflare Registrar | ~$10/year |
| Analytics | Plausible | $0 (self-host) or $9/month |
| Image gen | Agnes AI | Pay per image (~$0.05 each) |
| Email | Cloudflare Email Routing | $0 |
| Newsletter | (not using yet) | TBD |

Total fixed cost: domain only.
What I'd do differently
If I started over:
Skip Next.js, use Astro. Faster builds, smaller output, less JS shipped.
Use Cloudflare D1 for user accounts (free tier) instead of client-only.
Add Web Vitals tracking in CI to catch regressions.
Try it
The free Sourdough Hydration Calculator is live. Six preset hydration levels (Bagel, Sandwich, Classic, Country, Open Crumb, Ciabatta) cover most home-baking needs. No signup, no tracking beyond aggregate page views, no email collection.
For the underlying math, see the open baker's percentage guide on the same site. For a quick sanity check, try entering 500g flour + 75% hydration + 20% starter — you should get 287.5g water-to-add, 10g salt, and 850g total dough (before bake loss).
If you bake, I'd love to hear whether the defaults work for your kitchen. If you don't bake, I hope the math section was at least interesting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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