<?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: Julian Neagu</title>
    <description>The latest articles on DEV Community by Julian Neagu (@julianneagu).</description>
    <link>https://dev.to/julianneagu</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%2F3924638%2F65253828-e1af-46b7-8579-b0fc82291567.jpg</url>
      <title>DEV Community: Julian Neagu</title>
      <link>https://dev.to/julianneagu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julianneagu"/>
    <language>en</language>
    <item>
      <title>Why rel="noopener" Is Your Front Line Against Tab-Nabbing Phishing Attacks</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Tue, 25 Aug 2026 17:55:37 +0000</pubDate>
      <link>https://dev.to/julianneagu/why-relnoopener-is-your-front-line-against-tab-nabbing-phishing-attacks-162o</link>
      <guid>https://dev.to/julianneagu/why-relnoopener-is-your-front-line-against-tab-nabbing-phishing-attacks-162o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The &lt;code&gt;rel="noopener"&lt;/code&gt; attribute prevents tab-nabbing phishing attacks by severing the JavaScript &lt;code&gt;window.opener&lt;/code&gt; reference between your page and any new tab opened with &lt;code&gt;target="_blank"&lt;/code&gt;. Modern browsers apply it automatically, but explicit declaration remains essential for security compliance, older browser coverage, and performance isolation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you add &lt;code&gt;target="_blank"&lt;/code&gt; to a link, you're creating more than just a new tab. You're creating a JavaScript back-channel between two pages. The newly opened tab receives a reference to your original page through &lt;code&gt;window.opener&lt;/code&gt;, and that reference can be weaponized. A malicious site can redirect your tab to a phishing page while the user is distracted. The user switches back, sees what looks like your login screen, and types in their credentials.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical vulnerability. It's a documented attack vector with a name: tab-nabbing. And the mitigation is trivially simple. Add six characters to every external link: &lt;code&gt;rel="noopener"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How window.opener Creates a JavaScript Back-Channel
&lt;/h2&gt;

&lt;p&gt;When you write this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;html&lt;br&gt;
&amp;lt;a href="https://external-docs.com" target="_blank"&amp;gt;&lt;br&gt;
  Read the documentation&lt;br&gt;
&amp;lt;/a&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The browser opens &lt;code&gt;external-docs.com&lt;/code&gt; in a new tab. Nothing surprising there. But behind the scenes, the new tab gets a JavaScript object called &lt;code&gt;window.opener&lt;/code&gt; that points back to your original page.&lt;/p&gt;

&lt;p&gt;That reference is live. The external page can't read your DOM or access your cookies directly, because same-origin policy blocks cross-domain data access. But it &lt;strong&gt;can&lt;/strong&gt; do one thing that breaks the entire security model: it can change where your tab points.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Without &lt;code&gt;noopener&lt;/code&gt;, any page opened with &lt;code&gt;target="_blank"&lt;/code&gt; can redirect your original tab to a phishing site using &lt;code&gt;window.opener.location&lt;/code&gt;, even across completely different domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The external site runs this single line of JavaScript:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;javascript&lt;br&gt;
window.opener.location = "https://fake-login-page.com";&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your original tab, sitting in the background, now displays a convincing replica of your login page. The user doesn't notice immediately because they're focused on the new tab. When they switch back, they see "session expired" and re-enter their credentials. The attacker captures everything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubz6vkl8u8dbtxk8j1bl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fubz6vkl8u8dbtxk8j1bl.png" alt="Diagram showing tab-nabbing attack flow where external site uses window.opener to redirect original tab to phishing page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Attack Sequence
&lt;/h3&gt;

&lt;p&gt;Here's how tab-nabbing plays out in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User is logged into your app at &lt;code&gt;yourapp.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User clicks an external link that opens with &lt;code&gt;target="_blank"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The new tab loads, runs JavaScript that executes &lt;code&gt;window.opener.location = "https://fake-yourapp.com"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your original tab (sitting in the background) is now showing a fake login page&lt;/li&gt;
&lt;li&gt;User switches back, sees "session expired", types in credentials&lt;/li&gt;
&lt;li&gt;Attacker logs the credentials and redirects to the real login page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The attack works because changing another window's location is one of the few cross-origin operations browsers historically allowed. You can't read the other window's content, but you can send it somewhere else. That's all a phishing attack needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Benefit: Process Isolation
&lt;/h2&gt;

&lt;p&gt;Security is the main reason to use &lt;code&gt;noopener&lt;/code&gt;, but there's a secondary benefit: performance isolation.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;noopener&lt;/code&gt;, the new tab runs in the same browser process as your page. If the external site is resource-heavy (large JavaScript bundles, memory leaks, slow rendering), it can degrade your site's performance even though it's in a separate tab. Your UI might stutter. Your event loop might block.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Adding &lt;code&gt;rel="noopener"&lt;/code&gt; allows browsers to spawn the new tab in a completely separate process, preventing external sites from degrading your original page's performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you add &lt;code&gt;rel="noopener"&lt;/code&gt;, the browser can spawn the new tab in a completely separate process. That means a slow or broken external page won't jank your app's UI or consume your memory budget. This matters more on lower-end devices and mobile browsers where process isolation is less aggressive. It's not the primary reason to declare &lt;code&gt;noopener&lt;/code&gt;, but it's a measurable side benefit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2mvkutet9djlrz8fd4qg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2mvkutet9djlrz8fd4qg.jpeg" alt="VS Code editor showing HTML code with an anchor tag containing rel=" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What rel="noopener" Actually Does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;noopener&lt;/code&gt; attribute does exactly one thing: it sets &lt;code&gt;window.opener&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt; in the newly opened tab. The tab still opens. The link still works. The user experience is identical. But the new tab has no reference back to your page, which means it can't touch it.&lt;/p&gt;

&lt;p&gt;Here's the corrected version:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;html&lt;br&gt;
&amp;lt;a href="https://external-docs.com" target="_blank" rel="noopener"&amp;gt;&lt;br&gt;
  Read the documentation&lt;br&gt;
&amp;lt;/a&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's it. Six extra characters. The link behaves the same from the user's perspective, but the security hole is closed.&lt;/p&gt;

&lt;p&gt;If the external site is one you don't fully trust (user-generated content, third-party integrations, ads), you can also add &lt;code&gt;noreferrer&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;html&lt;br&gt;
&amp;lt;a href="https://sketchy-site.com" target="_blank" rel="noopener noreferrer"&amp;gt;&lt;br&gt;
  External link&lt;br&gt;
&amp;lt;/a&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;noreferrer&lt;/code&gt; part prevents your URL from being sent to the external site as the &lt;code&gt;Referer&lt;/code&gt; header. That's useful for privacy (the external site won't know where the traffic came from), but it's a separate concern. For most cases, &lt;code&gt;noopener&lt;/code&gt; alone is sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Modern Browsers Apply noopener Automatically?
&lt;/h2&gt;

&lt;p&gt;Yes, since 2021. Chrome version 88 made the change first. Firefox and Safari followed shortly after. Edge inherited the behavior when it switched to Chromium.&lt;/p&gt;

&lt;p&gt;If you're targeting only modern browsers, you're already protected by default. The browser treats every &lt;code&gt;target="_blank"&lt;/code&gt; as if it had &lt;code&gt;rel="noopener"&lt;/code&gt;, whether you wrote it or not.&lt;/p&gt;

&lt;p&gt;So why still write it explicitly?&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Reasons to Keep Writing rel="noopener"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Older browser support.&lt;/strong&gt; Chrome 88 was released in January 2021. Firefox 79 (released July 2020) implemented the same default. Safari added it in version 12.2 (March 2019). But users on older versions of these browsers, or on outdated mobile browsers, are still vulnerable. Explicit declaration protects everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security scanners and compliance.&lt;/strong&gt; Tools like &lt;a href="https://codeanalysis.dev/blog/static-code-analysis-detects-hidden-vulnerabilities" rel="noopener noreferrer"&gt;static code analysis checkers&lt;/a&gt; flag missing &lt;code&gt;noopener&lt;/code&gt; on &lt;code&gt;target="_blank"&lt;/code&gt; links as vulnerabilities. SOC2 audits and penetration tests will call it out. If you're working in fintech, healthcare, or enterprise SaaS, auditors expect to see explicit &lt;code&gt;noopener&lt;/code&gt; declarations. The fact that modern browsers handle it automatically doesn't satisfy the audit checklist.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security scanners, linters, and compliance audits flag missing &lt;code&gt;noopener&lt;/code&gt; on &lt;code&gt;target="_blank"&lt;/code&gt; links as vulnerabilities, making explicit declaration necessary for SOC2 and pen test compliance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Documentation and intent.&lt;/strong&gt; Code is read more often than it's written. When another developer sees &lt;code&gt;target="_blank"&lt;/code&gt; without &lt;code&gt;noopener&lt;/code&gt;, they don't know if the omission was intentional or accidental. Explicit declaration signals that the security consideration was addressed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8pse28cul9c04rg518ci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8pse28cul9c04rg518ci.png" alt="Split comparison showing window.opener returning object without noopener versus null with noopener attribute" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Developers Forget to Add noopener
&lt;/h2&gt;

&lt;p&gt;The most common mistake isn't forgetting to add &lt;code&gt;noopener&lt;/code&gt; to handwritten links. It's forgetting to add it to &lt;strong&gt;dynamically generated links&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links Generated from User Content
&lt;/h3&gt;

&lt;p&gt;If your app renders Markdown or rich text from users, and that content includes links, you need to ensure the renderer adds &lt;code&gt;noopener&lt;/code&gt; to every &lt;code&gt;target="_blank"&lt;/code&gt; link. Most Markdown libraries don't do this by default.&lt;/p&gt;

&lt;p&gt;For example, using a JavaScript Markdown library without configuring link safety:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;javascript&lt;br&gt;
// Unsafe: user Markdown links open without noopener&lt;br&gt;
const html = marked.parse(userContent);&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You need to configure the renderer:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;javascript&lt;br&gt;
const renderer = new marked.Renderer();&lt;br&gt;
renderer.link = (href, title, text) =&amp;gt; {&lt;br&gt;
  const titleAttr = title ?&lt;/code&gt; title="${title}"&lt;code&gt;: '';&lt;br&gt;
  return&lt;/code&gt;&lt;a href="${href}" rel="noopener noreferrer"&gt;${text}&lt;/a&gt;`;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;const html = marked.parse(userContent, { renderer });&lt;br&gt;
``&lt;/p&gt;

&lt;h3&gt;
  
  
  Links in Third-Party Embeds
&lt;/h3&gt;

&lt;p&gt;If your app embeds content from third parties (documentation widgets, comment systems, social media feeds), you don't control the HTML those systems inject. You need to sanitize it on your end.&lt;/p&gt;

&lt;p&gt;One approach: walk the DOM after the embed loads and add &lt;code&gt;noopener&lt;/code&gt; to any &lt;code&gt;target="_blank"&lt;/code&gt; link that's missing it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;javascript&lt;br&gt;
document.querySelectorAll('a[target="_blank"]:not([rel~="noopener"])').forEach(link =&amp;gt; {&lt;br&gt;
  const currentRel = link.getAttribute('rel') || '';&lt;br&gt;
  link.setAttribute('rel', `${currentRel} noopener`.trim());&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This catches external content you didn't write but are responsible for serving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the Risk Actually High?
&lt;/h2&gt;

&lt;p&gt;The probability of a tab-nabbing attack is low. Most external sites you link to are not malicious. Most users don't click links to sketchy domains from within your app. And modern browsers already mitigate the issue automatically.&lt;/p&gt;

&lt;p&gt;But the &lt;strong&gt;impact&lt;/strong&gt; is high. A successful tab-nabbing attack can compromise user credentials, leading to account takeover, data exfiltration, and reputational damage. The cost of mitigation is negligible: six extra characters. The risk is low-probability, high-severity. That math always favors the fix.&lt;/p&gt;

&lt;p&gt;From a &lt;a href="https://visionvix.com/claude-vs-gpt-for-coding-comparison-and-reviews/" rel="noopener noreferrer"&gt;strategic security perspective&lt;/a&gt;, &lt;code&gt;noopener&lt;/code&gt; is a defense-in-depth measure. It's not your only layer of protection, but it's a layer that costs nothing to add and closes a documented attack vector.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvml4ojwifcfose451ibk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvml4ojwifcfose451ibk.jpeg" alt="Chrome DevTools Elements panel showing HTML anchor tag with rel=" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Audit Your Codebase for Missing noopener
&lt;/h2&gt;

&lt;p&gt;If you're working on an existing codebase and want to find every external link that's missing &lt;code&gt;noopener&lt;/code&gt;, here's the audit process:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Search for target="_blank"
&lt;/h3&gt;

&lt;p&gt;Grep your codebase for every occurrence of &lt;code&gt;target="_blank"&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;On macOS or Linux:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bash&lt;br&gt;
grep -r 'target="_blank"' ./src&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On Windows PowerShell:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;powershell&lt;br&gt;
Select-String -Path .\src\* -Pattern 'target="_blank"' -Recurse&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This gives you a list of every file and line where a new-tab link exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Check for Missing noopener
&lt;/h3&gt;

&lt;p&gt;For each result, check whether the same line contains &lt;code&gt;rel="noopener"&lt;/code&gt; or &lt;code&gt;rel="noopener noreferrer"&lt;/code&gt;. If it doesn't, add it.&lt;/p&gt;

&lt;p&gt;You can automate this with a more specific grep:&lt;/p&gt;

&lt;p&gt;On macOS or Linux:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bash&lt;br&gt;
grep -r 'target="_blank"' ./src | grep -v 'noopener'&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;On Windows PowerShell:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;powershell&lt;br&gt;
Select-String -Path .\src\* -Pattern 'target="_blank"' -Recurse | Where-Object { $_.Line -notmatch 'noopener' }&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Any result from this search is a link that opens a new tab without &lt;code&gt;noopener&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add Linter Rules
&lt;/h3&gt;

&lt;p&gt;To prevent future regressions, add an ESLint rule that flags missing &lt;code&gt;noopener&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "rules": {&lt;br&gt;
    "react/jsx-no-target-blank": ["error", { "enforceDynamicLinks": "always" }]&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This rule enforces &lt;code&gt;noopener&lt;/code&gt; on every &lt;code&gt;target="_blank"&lt;/code&gt; link, including dynamically generated ones. It fails the build if the attribute is missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Automate in CI
&lt;/h3&gt;

&lt;p&gt;Add a script to your CI pipeline that runs the grep check and fails the build if any &lt;code&gt;target="_blank"&lt;/code&gt; link is missing &lt;code&gt;noopener&lt;/code&gt;. This makes the mitigation enforceable at the infrastructure level, not just at the code-review level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;rel="noopener"&lt;/code&gt; attribute is a one-line fix for a documented security vulnerability. Modern browsers apply it automatically, but explicit declaration remains necessary for compliance, older browser support, and defense-in-depth strategy.&lt;/p&gt;

&lt;p&gt;If you're writing new links, add &lt;code&gt;noopener&lt;/code&gt; to every &lt;code&gt;target="_blank"&lt;/code&gt; link. If you're auditing an existing codebase, search for &lt;code&gt;target="_blank"&lt;/code&gt;, check for missing &lt;code&gt;noopener&lt;/code&gt;, and add linter rules to prevent regressions. If you're rendering user-generated content or third-party embeds, sanitize the output to ensure every external link includes &lt;code&gt;noopener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The cost is six characters. The risk is account takeover. The fix is trivial. Ship it.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Why rel="noopener" Is Your Front Line Against Tab-Nabbing Phishing Attacks&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Security Vulnerability Hiding in Every target="_blank" Link&lt;/li&gt;
&lt;li&gt;How window.opener Turns Your External Links Into Phishing Attack Vectors&lt;/li&gt;
&lt;li&gt;rel="noopener" Explained: Stop Tab-Nabbing Attacks With Six Characters&lt;/li&gt;
&lt;li&gt;The JavaScript Back-Channel You Created By Forgetting rel="noopener"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;why-rel-noopener-prevents-tab-nabbing-phishing-attacks&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, security, javascript, performance&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to Embed a Free SaaS Audit Tool on Your Landing Page Without Breaking Production</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Thu, 20 Aug 2026 08:16:01 +0000</pubDate>
      <link>https://dev.to/julianneagu/how-to-embed-a-free-saas-audit-tool-on-your-landing-page-without-breaking-production-57pg</link>
      <guid>https://dev.to/julianneagu/how-to-embed-a-free-saas-audit-tool-on-your-landing-page-without-breaking-production-57pg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Building a free audit tool for your landing page means fighting CSS collisions, SSR failures, and SSRF vulnerabilities. An iframe solves isolation. A protected API route blocks attackers. Redis enforces rate limits. The entire setup runs at $0 on Vercel's free tier and handles 5,000 requests per day.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Embedding a working lite version of your SaaS product on your landing page is one of the highest-converting additions you can make. Users get instant value before signup. You build trust by letting them test the real thing. The upgrade path becomes obvious because they've already experienced what the full product does.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://websiteaudit.dev/" rel="noopener noreferrer"&gt;Website Audit Dev&lt;/a&gt; demonstrate this pattern well. A free scan delivers real value. Then it surfaces the full product at exactly the right moment. The conversion rate speaks for itself. But the technical implementation is where most teams stumble. This guide shows you how to build and deploy one the right way, without breaking your site in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosiq7mukume97fgeisxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosiq7mukume97fgeisxv.png" alt="VisionVix AI platform dashboard showing website audit results with 69 out of 100 score, displaying analytics, consent, SEO, and schema metrics with colored status bars&lt;br&gt;
" width="799" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Naive Approach Breaks in Production Every Time
&lt;/h2&gt;

&lt;p&gt;The simplest idea is to take your standalone HTML tool and paste it directly into your site's custom code panel or a Next.js component. You preview it locally. It renders. You deploy. The build succeeds. You open the production URL and everything is broken.&lt;/p&gt;

&lt;p&gt;This pattern repeats so predictably that you can set your watch by it. The reasons become obvious once you've hit them, but they're invisible until that moment.&lt;/p&gt;

&lt;p&gt;Your site has global CSS classes. Classes like &lt;code&gt;.layout&lt;/code&gt;, &lt;code&gt;.content&lt;/code&gt;, &lt;code&gt;.grid&lt;/code&gt;, &lt;code&gt;.button&lt;/code&gt;, and &lt;code&gt;.card&lt;/code&gt; are defined in your site's stylesheet. Your tool also has &lt;code&gt;.layout&lt;/code&gt;, &lt;code&gt;.content&lt;/code&gt;, &lt;code&gt;.grid&lt;/code&gt;, &lt;code&gt;.button&lt;/code&gt;, and &lt;code&gt;.card&lt;/code&gt;. They collide. Your tool's button suddenly has your site's hover state. Your site's grid now has your tool's column sizing. The cascade is fighting itself and there's no clean winner.&lt;/p&gt;

&lt;p&gt;Your site has a dark theme. Your tool expects light. The tool's white text on a white background becomes invisible. Or your site's dark background bleeds through the tool's transparent sections and makes the UI unreadable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnlyd1s1f5wsc3z7hqmw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnlyd1s1f5wsc3z7hqmw.png" alt="CSS collision showing conflicting button and grid styles between embedded tool and parent site" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your tool uses &lt;code&gt;localStorage&lt;/code&gt; to cache results. This works in the browser. It throws during server-side rendering because &lt;code&gt;window&lt;/code&gt; and &lt;code&gt;localStorage&lt;/code&gt; don't exist on the server. Next.js tries to render the component at build time, hits &lt;code&gt;localStorage.getItem()&lt;/code&gt;, and the build fails with a cryptic error about undefined properties.&lt;/p&gt;

&lt;p&gt;Your tool loads Three.js via a runtime-injected script tag to render a WebGL globe. Your site has a Content Security Policy header that blocks inline scripts and third-party script sources. The browser silently refuses to execute the injected script. Your globe never appears. The console shows a CSP violation but only if you know to check the Network tab's failed requests.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;And if any of this produces malformed HTML, like an unclosed tag or a mismatched quote, Vercel's build pipeline kills the deploy. You get &lt;code&gt;next build exited with 1&lt;/code&gt; and no useful error message.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The browser would have silently recovered from the same HTML. Vercel does not.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Fix Is an Iframe
&lt;/h2&gt;

&lt;p&gt;The fix is an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. It sounds old-fashioned, but it's exactly right here.&lt;/p&gt;

&lt;p&gt;An iframe is a fully isolated browser context. Your site's CSS cannot reach inside it. Your site's JavaScript cannot touch its DOM. Your site's theme, CSP headers, and global state are invisible to the iframe's content. The tool renders exactly as it does standalone, pixel-perfect, every time.&lt;/p&gt;

&lt;p&gt;The setup has three parts. Each one is simple. Together they solve the entire class of problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk22hdkd16zakwjttqpfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk22hdkd16zakwjttqpfy.png" alt="Diagram showing iframe creating isolated browser context with separate CSS, JavaScript, and DOM from parent page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Tool Itself
&lt;/h3&gt;

&lt;p&gt;The tool is a single HTML file. It contains all its CSS in a &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block and all its JavaScript in a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block. No external dependencies unless you need them. If you do need a library like Three.js or Chart.js, load it from a CDN with an integrity hash.&lt;/p&gt;

&lt;p&gt;This file lives in your Next.js project's &lt;code&gt;public/&lt;/code&gt; folder. Vercel serves everything in &lt;code&gt;public/&lt;/code&gt; as static files at the root path. If you put &lt;code&gt;audit-tool.html&lt;/code&gt; in &lt;code&gt;public/&lt;/code&gt;, it's available at &lt;code&gt;https://yoursite.com/audit-tool.html&lt;/code&gt;. No routing config needed.&lt;/p&gt;

&lt;p&gt;Your landing page embeds it with one iframe tag:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;html&lt;br&gt;
&amp;lt;iframe&lt;br&gt;
  src="/audit-tool.html"&lt;br&gt;
  width="100%"&lt;br&gt;
  height="600"&lt;br&gt;
  frameborder="0"&lt;br&gt;
  sandbox="allow-scripts allow-same-origin"&lt;br&gt;
  title="Free Audit Tool"&amp;gt;&lt;br&gt;
&amp;lt;/iframe&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sandbox&lt;/code&gt; attribute is key. &lt;code&gt;allow-scripts&lt;/code&gt; lets the tool's JavaScript run. &lt;code&gt;allow-same-origin&lt;/code&gt; lets it access &lt;code&gt;localStorage&lt;/code&gt; and make fetch calls to your API route. Without &lt;code&gt;allow-same-origin&lt;/code&gt;, the iframe is treated as cross-origin even though it's on the same domain, and fetch calls to &lt;code&gt;/api/*&lt;/code&gt; fail with CORS errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvdbe7ewsa00g95qm4fj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkvdbe7ewsa00g95qm4fj.png" alt="Code snippet showing iframe sandbox attribute with allow-scripts and allow-same-origin permissions" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Backend Route
&lt;/h3&gt;

&lt;p&gt;The backend route is a Next.js App Router serverless function at &lt;code&gt;app/api/audit/route.js&lt;/code&gt;. Its only job is to fetch a URL server-side and return the HTML response plus the real HTTP response headers.&lt;/p&gt;

&lt;p&gt;This matters because &lt;strong&gt;browsers hide cross-origin response headers from client-side JavaScript&lt;/strong&gt;. If your tool fetches &lt;code&gt;https://example.com&lt;/code&gt; from the browser, the response headers like &lt;code&gt;Strict-Transport-Security&lt;/code&gt;, &lt;code&gt;X-Frame-Options&lt;/code&gt;, and &lt;code&gt;Content-Security-Policy&lt;/code&gt; are invisible. The browser returns an opaque response object and &lt;code&gt;response.headers.get('x-frame-options')&lt;/code&gt; is always null.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security checks that rely on HTTP headers can only be read when fetched from a server. That's the entire reason this route exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The route also enforces rate limiting, which we'll cover in the next section.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Vercel's Free Tier Doesn't Give You Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Vercel's free tier does not support built-in rate limiting through &lt;code&gt;vercel.json&lt;/code&gt; configuration or dashboard settings. You can't add a rate limit rule that says "block IPs that hit this route more than 30 times in 60 seconds."&lt;/p&gt;

&lt;p&gt;The second problem is that Vercel serverless functions are stateless. They reset on every cold start. If you try to implement in-memory rate limiting like this:&lt;/p&gt;

&lt;p&gt;``javascript&lt;br&gt;
// This DOES NOT WORK in production&lt;br&gt;
const requestCounts = {};&lt;/p&gt;

&lt;p&gt;export async function GET(req) {&lt;br&gt;
  const ip = req.headers.get('x-forwarded-for');&lt;br&gt;
  requestCounts[ip] = (requestCounts[ip] || 0) + 1;&lt;br&gt;
  if (requestCounts[ip] &amp;gt; 30) {&lt;br&gt;
    return new Response('Rate limit exceeded', { status: 429 });&lt;br&gt;
  }&lt;br&gt;
  // ... rest of logic&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;This works locally. It fails in production because &lt;code&gt;requestCounts&lt;/code&gt; is wiped on every cold start. A cold start happens when Vercel spins up a new instance of your function. This happens unpredictably, often multiple times per day. The attacker sends 10 requests, triggers a cold start, and the counter resets to zero. They repeat this forever.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs4ecoloq8hfdvzxwhl8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs4ecoloq8hfdvzxwhl8g.png" alt="Dashboard showing Redis database metrics with 36 commands, 42B storage, $0.00 cost, and bar charts displaying daily commands and bandwidth by region." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need persistent storage. That storage needs to be fast enough to respond in under 50ms so it doesn't add noticeable latency to every request. Redis is the right answer here.&lt;/p&gt;
&lt;h3&gt;
  
  
  How Upstash Redis Solves This
&lt;/h3&gt;

&lt;p&gt;Upstash offers a Redis service with a free tier that provides &lt;strong&gt;10,000 commands per day&lt;/strong&gt;. Each audit request consumes 2 Redis commands: one &lt;code&gt;GET&lt;/code&gt; to read the current count for an IP, and one &lt;code&gt;INCR&lt;/code&gt; to increment it. At 10,000 commands per day, the system can handle &lt;strong&gt;5,000 audit requests per day&lt;/strong&gt; before hitting the free tier limit.&lt;/p&gt;

&lt;p&gt;The rate limit is set at &lt;strong&gt;30 requests per IP address within 60 seconds&lt;/strong&gt; before returning a 429 response. This is enough to block simple abuse while allowing legitimate users to run multiple tests.&lt;/p&gt;

&lt;p&gt;Here's the full implementation:&lt;/p&gt;

&lt;p&gt;``javascript&lt;br&gt;
// app/api/audit/route.js&lt;br&gt;
import { Redis } from '@upstash/redis';&lt;/p&gt;

&lt;p&gt;const redis = new Redis({&lt;br&gt;
  url: process.env.UPSTASH_REDIS_REST_URL,&lt;br&gt;
  token: process.env.UPSTASH_REDIS_REST_TOKEN,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export async function GET(req) {&lt;br&gt;
  const ip = req.headers.get('x-forwarded-for') || 'unknown';&lt;br&gt;
  const rateKey = &lt;code&gt;rate:${ip}&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;// Check current count&lt;br&gt;
  const current = await redis.get(rateKey);&lt;br&gt;
  if (current &amp;amp;&amp;amp; current &amp;gt;= 30) {&lt;br&gt;
    return new Response('Rate limit exceeded. Try again in 60 seconds.', { &lt;br&gt;
      status: 429 &lt;br&gt;
    });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Increment and set expiry if first request&lt;br&gt;
  const count = await redis.incr(rateKey);&lt;br&gt;
  if (count === 1) {&lt;br&gt;
    await redis.expire(rateKey, 60);&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// ... rest of audit logic&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;expire&lt;/code&gt; call sets a 60-second TTL on the key. After 60 seconds, Redis automatically deletes the key. The next request from that IP starts fresh at count 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1jp54ue7ms5quq2qyhi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg1jp54ue7ms5quq2qyhi.jpg" alt="Environment Variables page showing two sensitive variables: UPSTASH_REDIS_REST_TOKEN and UPSTASH_REDIS_REST_URL, both added recently" width="799" height="571"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  SSRF Protection: Why Your API Route Is a Security Hole Without It
&lt;/h2&gt;

&lt;p&gt;A server-side fetch endpoint without SSRF protection is a security vulnerability. SSRF stands for Server-Side Request Forgery. It means an attacker can make your server fetch internal URLs that are not accessible from the public internet.&lt;/p&gt;

&lt;p&gt;Here's what happens if you don't protect it:&lt;/p&gt;

&lt;p&gt;``javascript&lt;br&gt;
// VULNERABLE CODE - DO NOT USE&lt;br&gt;
export async function GET(req) {&lt;br&gt;
  const { searchParams } = new URL(req.url);&lt;br&gt;
  const targetUrl = searchParams.get('url');&lt;/p&gt;

&lt;p&gt;const response = await fetch(targetUrl);&lt;br&gt;
  const html = await response.text();&lt;br&gt;
  return new Response(html);&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;An attacker sends this request:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http&lt;br&gt;
GET /api/audit?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That IP address is AWS's EC2 metadata endpoint. It's only accessible from inside an EC2 instance. If your Vercel function runs on AWS infrastructure (which it often does), this request succeeds. The attacker gets temporary AWS credentials. They use those credentials to access your S3 buckets, RDS databases, and Lambda functions.&lt;/p&gt;

&lt;p&gt;The same attack works with internal services. If your company has an internal admin panel at &lt;code&gt;http://admin.internal.company.com&lt;/code&gt;, an attacker can make your server fetch it and return the HTML. They now have access to internal tools that should never be public.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Five Defensive Layers
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy5rnxjd89yflkge2u2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvy5rnxjd89yflkge2u2m.png" alt="Network inspector showing CORS errors when iframe lacks allow-same-origin sandbox attribute" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution is to validate and sanitize every URL before fetching it. Here's the full defensive stack:&lt;/p&gt;

&lt;p&gt;``javascript&lt;br&gt;
// app/api/audit/route.js (continued)&lt;br&gt;
const BLOCKED_IPS = [&lt;br&gt;
  '127.0.0.0/8',      // localhost&lt;br&gt;
  '169.254.0.0/16',   // AWS metadata&lt;br&gt;
  '10.0.0.0/8',       // private network&lt;br&gt;
  '172.16.0.0/12',    // private network&lt;br&gt;
  '192.168.0.0/16',   // private network&lt;br&gt;
];&lt;/p&gt;

&lt;p&gt;function isBlockedIP(hostname) {&lt;br&gt;
  // Parse hostname to IP if it's an IP address&lt;br&gt;
  const ipRegex = /^(\d{1,3}.){3}\d{1,3}$/;&lt;br&gt;
  if (!ipRegex.test(hostname)) return false;&lt;/p&gt;

&lt;p&gt;const parts = hostname.split('.').map(Number);&lt;/p&gt;

&lt;p&gt;// Check against each blocked range&lt;br&gt;
  if (parts[0] === 127) return true;                    // localhost&lt;br&gt;
  if (parts[0] === 169 &amp;amp;&amp;amp; parts[1] === 254) return true; // AWS metadata&lt;br&gt;
  if (parts[0] === 10) return true;                      // private&lt;br&gt;
  if (parts[0] === 172 &amp;amp;&amp;amp; parts[1] &amp;gt;= 16 &amp;amp;&amp;amp; parts[1] &amp;lt;= 31) return true;&lt;br&gt;
  if (parts[0] === 192 &amp;amp;&amp;amp; parts[1] === 168) return true;&lt;/p&gt;

&lt;p&gt;return false;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export async function GET(req) {&lt;br&gt;
  const { searchParams } = new URL(req.url);&lt;br&gt;
  const targetUrl = searchParams.get('url');&lt;/p&gt;

&lt;p&gt;// Defense 1: Reject missing or empty URLs&lt;br&gt;
  if (!targetUrl) {&lt;br&gt;
    return new Response('Missing url parameter', { status: 400 });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Defense 2: Cap URL length to prevent attack payloads&lt;br&gt;
  if (targetUrl.length &amp;gt; 2048) {&lt;br&gt;
    return new Response('URL too long', { status: 400 });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Defense 3: Parse and validate the URL&lt;br&gt;
  let parsedUrl;&lt;br&gt;
  try {&lt;br&gt;
    parsedUrl = new URL(targetUrl);&lt;br&gt;
  } catch (e) {&lt;br&gt;
    return new Response('Invalid URL format', { status: 400 });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Defense 4: Block non-HTTP(S) protocols&lt;br&gt;
  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {&lt;br&gt;
    return new Response('Only HTTP(S) allowed', { status: 400 });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Defense 5: Block private/internal IPs&lt;br&gt;
  if (isBlockedIP(parsedUrl.hostname)) {&lt;br&gt;
    return new Response('Access to internal resources blocked', { status: 403 });&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// All checks passed, safe to fetch&lt;br&gt;
  const controller = new AbortController();&lt;br&gt;
  const timeout = setTimeout(() =&amp;gt; controller.abort(), 10000);&lt;/p&gt;

&lt;p&gt;try {&lt;br&gt;
    const response = await fetch(parsedUrl.href, { signal: controller.signal });&lt;br&gt;
    const html = await response.text();&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return new Response(JSON.stringify({
  html,
  headers: Object.fromEntries(response.headers.entries()),
  status: response.status,
}), {
  headers: { 'Content-Type': 'application/json' },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;} catch (e) {&lt;br&gt;
    if (e.name === 'AbortError') {&lt;br&gt;
      return new Response('Request timeout', { status: 504 });&lt;br&gt;
    }&lt;br&gt;
    return new Response('Fetch failed', { status: 500 });&lt;br&gt;
  } finally {&lt;br&gt;
    clearTimeout(timeout);&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL length is capped at 2,048 characters&lt;/strong&gt; to prevent attack payloads. &lt;strong&gt;Fetch timeout is enforced at 10 seconds&lt;/strong&gt; using &lt;code&gt;AbortController&lt;/code&gt;. If the target site doesn't respond within 10 seconds, the request is aborted and the user gets a 504 error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy91uw1rxcclzxgjd9a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy91uw1rxcclzxgjd9a5.png" alt="Next.js App Router file structure with API route at app/api/audit/route.js" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What Breaks and How to Debug It
&lt;/h2&gt;

&lt;p&gt;Here's what goes wrong in production and how to fix it.&lt;/p&gt;
&lt;h3&gt;
  
  
  CSS Collisions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Your tool's buttons have the wrong color. Your site's font leaks into the tool. The layout shifts unexpectedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Your site's global CSS is bleeding into the iframe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Make sure the iframe has &lt;code&gt;sandbox="allow-scripts allow-same-origin"&lt;/code&gt;. If the problem persists, the tool's HTML might be loading your site's stylesheet. Check the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; block in &lt;code&gt;audit-tool.html&lt;/code&gt;. Remove any &lt;code&gt;&amp;lt;link rel="stylesheet"&amp;gt;&lt;/code&gt; tags that point to your site's CSS.&lt;/p&gt;
&lt;h3&gt;
  
  
  CORS Errors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; The browser console shows &lt;code&gt;Blocked by CORS policy&lt;/code&gt;. The tool can't fetch data from your API route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The iframe doesn't have &lt;code&gt;allow-same-origin&lt;/code&gt; in the &lt;code&gt;sandbox&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add &lt;code&gt;allow-same-origin&lt;/code&gt; to the iframe tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;sandbox=&lt;/span&gt;&lt;span class="s"&gt;"allow-scripts allow-same-origin"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcze6cd6551emizhj80f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkcze6cd6551emizhj80f.png" alt="Security audit dashboard showing websiteaudit.dev with risk score of 79/100, displaying 3 critical issues, 1 warning, 13 passing checks across multiple security categories" width="799" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more detailed security patterns in user-facing tools, &lt;a href="https://securityaudit.dev/" rel="noopener noreferrer"&gt;Security Audit Dev&lt;/a&gt; demonstrates a similar multi-layer validation approach before running any automated checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limiting Doesn't Work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You've added the Redis rate limiting code but users can still send unlimited requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The environment variables aren't set in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Go to your Vercel dashboard. Click your project. Click "Settings" → "Environment Variables". Add &lt;code&gt;UPSTASH_REDIS_REST_URL&lt;/code&gt; and &lt;code&gt;UPSTASH_REDIS_REST_TOKEN&lt;/code&gt;. Redeploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch Returns 403 for Valid URLs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Your tool returns "Access to internal resources blocked" for legitimate public websites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;isBlockedIP&lt;/code&gt; function is blocking valid hostnames.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; The current implementation only blocks numeric IPs. If you're seeing false positives, check if the hostname resolves to a private IP. You might need to add a DNS lookup step and validate the resolved IP instead of the hostname.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3c1twdru49tl0ky96hb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw3c1twdru49tl0ky96hb.png" alt="Browser developer tools displaying opaque response headers that are hidden from client-side fetch requests" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Here's the step-by-step process to ship this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create the tool HTML file&lt;/strong&gt;. Put it in &lt;code&gt;public/audit-tool.html&lt;/code&gt;. Test it standalone by opening &lt;code&gt;http://localhost:3000/audit-tool.html&lt;/code&gt; in your browser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the iframe to your landing page&lt;/strong&gt;. Use the full &lt;code&gt;sandbox&lt;/code&gt; attribute shown earlier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create the API route&lt;/strong&gt;. Put the full code from this guide in &lt;code&gt;app/api/audit/route.js&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sign up for Upstash&lt;/strong&gt;. Create a new Redis database. Copy the REST URL and REST token.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set environment variables&lt;/strong&gt;. Add &lt;code&gt;UPSTASH_REDIS_REST_URL&lt;/code&gt; and &lt;code&gt;UPSTASH_REDIS_REST_TOKEN&lt;/code&gt; to your local &lt;code&gt;.env.local&lt;/code&gt; and to Vercel's dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test locally&lt;/strong&gt;. Run &lt;code&gt;npm run dev&lt;/code&gt;. Open your landing page. Try the tool. Check the Network tab for errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy to Vercel&lt;/strong&gt;. Run &lt;code&gt;vercel --prod&lt;/code&gt;. Open the production URL. Test again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor rate limits&lt;/strong&gt;. Check your Upstash dashboard to see how many commands you're using per day. If you hit the free tier limit, upgrade or adjust your rate limit values.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building a free audit tool is one of the highest-leverage additions to your SaaS landing page. The iframe gives you perfect isolation. The API route blocks SSRF attacks. Redis enforces rate limits. The entire setup runs at $0 on Vercel's free tier and handles 5,000 requests per day.&lt;/p&gt;

&lt;p&gt;For more on building automated workflows that tie into tools like this, check out &lt;a href="https://workflowgenerator.dev/blog/building-your-first-automated-workflow-step" rel="noopener noreferrer"&gt;this step-by-step guide on building your first automated workflow&lt;/a&gt;. And if you're looking for more ways to optimize your landing page strategy, the principles in &lt;a href="https://visionvix.com/landing-page-best-practices/" rel="noopener noreferrer"&gt;landing page best practices&lt;/a&gt; apply directly here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49dn3i7wy8uwpdw7leib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F49dn3i7wy8uwpdw7leib.png" alt="Comparison of rate limiting approaches showing Vercel's serverless function limitations on free tier" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; How to Embed a Free SaaS Audit Tool on Your Landing Page Without Breaking Production&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building a Landing Page Audit Tool That Survives CSS Collisions, SSR, and Security Attacks&lt;/li&gt;
&lt;li&gt;The Right Way to Embed a Free Tool Demo on Your SaaS Landing Page (Iframe + Redis + Vercel)&lt;/li&gt;
&lt;li&gt;Stop Breaking Your Landing Page: How to Safely Embed a Free Audit Tool Using Iframes&lt;/li&gt;
&lt;li&gt;From CSS Chaos to Production-Ready: Building a Free Landing Page Audit Tool for $0&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;embed-free-saas-audit-tool-landing-page-iframe-vercel&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, tutorial, performance, iframe&lt;/p&gt;

</description>
      <category>iframe</category>
      <category>performance</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Putting Everything in functions.php: The WordPress File Separation Guide</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:51:06 +0000</pubDate>
      <link>https://dev.to/julianneagu/stop-putting-everything-in-functionsphp-the-wordpress-file-separation-guide-13gd</link>
      <guid>https://dev.to/julianneagu/stop-putting-everything-in-functionsphp-the-wordpress-file-separation-guide-13gd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; WordPress gives you three files with distinct jobs: functions.php for logic, style.css for visual rules, and theme.json for design tokens. Mix them up, and your theme becomes unmaintainable. Keep them separate, and you get a scalable design system that works across every block, page, and brand update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most developers dump everything into functions.php because it's the first file they learned to edit. Custom fonts go there. Visual styling gets scattered across PHP templates. Color palettes live in hardcoded inline styles. Then someone asks you to change the primary brand color, and you spend two hours hunting through fifteen files trying to find every instance.&lt;/p&gt;

&lt;p&gt;This isn't just messy. It breaks the separation of concerns that makes modern WordPress themes maintainable. WordPress gives you three files that each do one job extremely well. When you understand what belongs in each file and why, building themes becomes faster, handoffs become cleaner, and design iterations don't break functionality.&lt;/p&gt;

&lt;p&gt;Let's walk through exactly what each file does, what belongs where, and how they work together to create a cohesive design system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgua1n1e93i2pdofu35ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgua1n1e93i2pdofu35ac.png" alt="Code editor showing WordPress theme folder with functions.php, style.css, and theme.json files highlighted in sidebar" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  functions.php Is Your Loader, Not Your Styler
&lt;/h2&gt;

&lt;p&gt;This is the most misunderstood file in WordPress theme development. Developers treat it like a dumping ground for everything, but it has one clear purpose: &lt;strong&gt;load resources and enable features&lt;/strong&gt;. It's a loader, not a styler.&lt;/p&gt;

&lt;p&gt;functions.php tells WordPress what exists. It registers capabilities, enqueues assets, and adds hooks. But it should never apply visual presentation directly. No inline styles. No hardcoded CSS rules buried inside PHP functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Belongs in functions.php
&lt;/h3&gt;

&lt;p&gt;Here's the complete list of what this file should handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enqueue custom fonts from Google Fonts or local files&lt;/li&gt;
&lt;li&gt;Enqueue custom CSS files (blog.css, blocks.css, components.css)&lt;/li&gt;
&lt;li&gt;Enqueue JavaScript files for interactive features&lt;/li&gt;
&lt;li&gt;Register theme supports (custom logo, post thumbnails, title tag)&lt;/li&gt;
&lt;li&gt;Register custom image sizes for responsive layouts&lt;/li&gt;
&lt;li&gt;Register custom Gutenberg block styles and variations&lt;/li&gt;
&lt;li&gt;Register navigation menus and widget areas&lt;/li&gt;
&lt;li&gt;Add filters for custom content (reading time, table of contents injection)&lt;/li&gt;
&lt;li&gt;Add shortcodes for reusable content blocks&lt;/li&gt;
&lt;li&gt;Register custom blocks with block.json manifests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's say you want to load the Inter typeface from Google Fonts. This is exactly what functions.php should do:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php&lt;br&gt;
function visionvix_enqueue_fonts() {&lt;br&gt;
    wp_enqueue_style(&lt;br&gt;
        'visionvix-fonts',&lt;br&gt;
        'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&amp;amp;display=swap',&lt;br&gt;
        false&lt;br&gt;
    );&lt;br&gt;
}&lt;br&gt;
add_action('wp_enqueue_scripts', 'visionvix_enqueue_fonts');&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This code loads the font into the page's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section. That's it. It doesn't apply the font to any element. It doesn't set font sizes. It doesn't touch typography rules. It just makes the resource available.&lt;/p&gt;

&lt;p&gt;The same pattern applies when you want to load custom CSS conditionally. Maybe you have specific styling for blog articles that shouldn't load on product pages:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;php&lt;br&gt;
function visionvix_enqueue_blog_styles() {&lt;br&gt;
    if (is_singular('post')) {&lt;br&gt;
        wp_enqueue_style(&lt;br&gt;
            'visionvix-blog-styles',&lt;br&gt;
            get_template_directory_uri() . '/assets/css/blog.css',&lt;br&gt;
            array(),&lt;br&gt;
            '1.0.0'&lt;br&gt;
        );&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
add_action('wp_enqueue_scripts', 'visionvix_enqueue_blog_styles');&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This checks if you're viewing a single post, then loads &lt;code&gt;blog.css&lt;/code&gt; only on that post type. The visual rules live in the CSS file, not in this PHP function.&lt;/p&gt;

&lt;p&gt;If you're working with AI agents to generate or audit content, making your site structure clear and predictable helps those systems understand your intent. Following established patterns like &lt;a href="https://visionvix.com/json-formatting-best-practices/" rel="noopener noreferrer"&gt;proper JSON formatting best practices&lt;/a&gt; and ensuring your markup is &lt;a href="https://aiagentready.dev/blog/make-your-website-ai-agent-friendly" rel="noopener noreferrer"&gt;AI agent-friendly&lt;/a&gt; makes your site more maintainable by both humans and automated tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;functions.php enables features and registers resources. It never applies visual styling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This separation is the foundation of maintainability. When you keep functions.php focused on logic and registration, you can swap out entire styling layers without touching functionality. When you mix them, every design change risks breaking a feature. You can't hand off styling to a designer if it's tangled with PHP logic. You can't test visual changes in isolation if they're scattered across procedural code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1ehgn3uu881ty95tr92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv1ehgn3uu881ty95tr92.png" alt="Side-by-side comparison showing scattered CSS files (wrong approach) versus organized centralized CSS structure (correct approach)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Doesn't Belong Here
&lt;/h3&gt;

&lt;p&gt;Never put these in functions.php:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inline CSS rules written inside PHP strings&lt;/li&gt;
&lt;li&gt;Direct font-family or color declarations&lt;/li&gt;
&lt;li&gt;Hardcoded spacing values (margin, padding)&lt;/li&gt;
&lt;li&gt;Layout rules (flexbox, grid, positioning)&lt;/li&gt;
&lt;li&gt;Typography scales or responsive breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it changes how something looks on the page, it doesn't belong in functions.php. Period.&lt;/p&gt;

&lt;h2&gt;
  
  
  style.css Is Your Paintbrush
&lt;/h2&gt;

&lt;p&gt;This is where every visual rule lives. If it affects how something appears in the browser, it goes in &lt;code&gt;style.css&lt;/code&gt; or a CSS file you enqueue from functions.php.&lt;/p&gt;

&lt;p&gt;Think of style.css as the styling layer. It applies visual presentation to everything that functions.php loads. The font you enqueued gets applied here. The colors you want to use get declared here. The spacing between headings gets defined here.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Belongs in style.css
&lt;/h3&gt;

&lt;p&gt;Here's what you should put in your main stylesheet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global font-family declarations for body, headings, and UI elements&lt;/li&gt;
&lt;li&gt;Font sizes and weights for your complete typography scale&lt;/li&gt;
&lt;li&gt;Color variables or direct color values for text, backgrounds, and accents&lt;/li&gt;
&lt;li&gt;Spacing rules (margin, padding) for consistent vertical rhythm&lt;/li&gt;
&lt;li&gt;Layout rules (flexbox, grid, positioning) for page structure&lt;/li&gt;
&lt;li&gt;Blog article typography with reading-optimized line height and spacing&lt;/li&gt;
&lt;li&gt;Code block styling with syntax highlighting and background colors&lt;/li&gt;
&lt;li&gt;Highlight boxes, callouts, and blockquote styles&lt;/li&gt;
&lt;li&gt;Table of contents styling with indentation and hover states&lt;/li&gt;
&lt;li&gt;Reading time indicator positioning and color&lt;/li&gt;
&lt;li&gt;Custom dividers and signature footer styling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's continue the Inter font example. In functions.php, you loaded the font. Now in style.css, you apply it to actual elements:&lt;/p&gt;

&lt;p&gt;``css&lt;br&gt;
body {&lt;br&gt;
    font-family: 'Inter', sans-serif;&lt;br&gt;
    color: #111;&lt;br&gt;
    line-height: 1.7;&lt;br&gt;
    font-size: 16px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1, h2, h3 {&lt;br&gt;
    font-weight: 600;&lt;br&gt;
    letter-spacing: -0.02em;&lt;br&gt;
    margin-top: 2rem;&lt;br&gt;
    margin-bottom: 1rem;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h1 {&lt;br&gt;
    font-size: 2.5rem;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h2 {&lt;br&gt;
    font-size: 2rem;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;h3 {&lt;br&gt;
    font-size: 1.5rem;&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;This is where the visual transformation happens. Every pixel of spacing, every color choice, every typographic detail gets defined here. The user never sees your functions.php code. They see the output of your CSS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Premium Blog Aesthetic
&lt;/h3&gt;

&lt;p&gt;If you're building a content-focused site, style.css is where you define the reading experience. Code blocks get their dark background and syntax highlighting here. Pull quotes get their border and italic treatment here. The table of contents gets its indentation, numbering, and hover states here.&lt;/p&gt;

&lt;p&gt;Here's a concrete example for code blocks:&lt;/p&gt;

&lt;p&gt;``css&lt;br&gt;
pre {&lt;br&gt;
    background: #1e1e1e;&lt;br&gt;
    border-radius: 8px;&lt;br&gt;
    padding: 1.5rem;&lt;br&gt;
    overflow-x: auto;&lt;br&gt;
    margin: 2rem 0;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;code {&lt;br&gt;
    font-family: 'Fira Code', monospace;&lt;br&gt;
    font-size: 14px;&lt;br&gt;
    line-height: 1.6;&lt;br&gt;
    color: #d4d4d4;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.hljs-keyword {&lt;br&gt;
    color: #569cd6;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.hljs-string {&lt;br&gt;
    color: #ce9178;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.hljs-function {&lt;br&gt;
    color: #dcdcaa;&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;Every visual detail lives in CSS. No scattered inline styles. No mystery styles buried in block patterns. When you need to change the brand color, you change it in one place. When you need to adjust spacing, you update the CSS variable and it propagates everywhere.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Centralizing your visual rules in style.css means every design change happens in one predictable location.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One pattern I see constantly: developers write inline styles in PHP templates or scatter CSS across multiple files with no naming convention. That's a maintainability nightmare. When you need to debug why a button looks wrong, you shouldn't have to grep through twenty files to find the rule. Centralize your visual rules in style.css or a small set of well-named files like &lt;code&gt;blog.css&lt;/code&gt;, &lt;code&gt;blocks.css&lt;/code&gt;, or &lt;code&gt;components.css&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  theme.json Is Your Design System
&lt;/h2&gt;

&lt;p&gt;This is the modern WordPress approach to design tokens. If you're working with the block editor or building a block theme, theme.json is essential. It's not optional anymore.&lt;/p&gt;

&lt;p&gt;Think of theme.json as your design system configuration file. It defines reusable tokens - colors, font sizes, spacing values - that WordPress uses everywhere. The editor uses these tokens. The frontend uses these tokens. Custom blocks inherit these tokens. Core blocks respect these tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Belongs in theme.json
&lt;/h3&gt;

&lt;p&gt;Here's what this file controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global typography scale with named sizes (small, base, large, extra-large)&lt;/li&gt;
&lt;li&gt;Global color palette with semantic names (primary, secondary, accent, background, text)&lt;/li&gt;
&lt;li&gt;Global spacing scale with consistent values (tight, base, loose, extra-loose)&lt;/li&gt;
&lt;li&gt;Global layout rules (content width, wide width for constrained layouts)&lt;/li&gt;
&lt;li&gt;Block editor defaults (which blocks are enabled, which settings appear in the sidebar)&lt;/li&gt;
&lt;li&gt;Block editor restrictions (disable custom colors, lock down font sizes)&lt;/li&gt;
&lt;li&gt;Custom block styles (alternative visual treatments for core blocks)&lt;/li&gt;
&lt;li&gt;Link color rules (default link color, hover state)&lt;/li&gt;
&lt;li&gt;Button appearance defaults (padding, border radius, background color)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a simplified example showing the structure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "version": 2,&lt;br&gt;
  "settings": {&lt;br&gt;
    "color": {&lt;br&gt;
      "palette": [&lt;br&gt;
        {&lt;br&gt;
          "slug": "primary",&lt;br&gt;
          "color": "#0066cc",&lt;br&gt;
          "name": "Primary"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "accent",&lt;br&gt;
          "color": "#ff6b35",&lt;br&gt;
          "name": "Accent"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "background",&lt;br&gt;
          "color": "#ffffff",&lt;br&gt;
          "name": "Background"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "text",&lt;br&gt;
          "color": "#111111",&lt;br&gt;
          "name": "Text"&lt;br&gt;
        }&lt;br&gt;
      ]&lt;br&gt;
    },&lt;br&gt;
    "typography": {&lt;br&gt;
      "fontSizes": [&lt;br&gt;
        {&lt;br&gt;
          "slug": "small",&lt;br&gt;
          "size": "14px",&lt;br&gt;
          "name": "Small"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "base",&lt;br&gt;
          "size": "16px",&lt;br&gt;
          "name": "Base"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "large",&lt;br&gt;
          "size": "24px",&lt;br&gt;
          "name": "Large"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "slug": "extra-large",&lt;br&gt;
          "size": "32px",&lt;br&gt;
          "name": "Extra Large"&lt;br&gt;
        }&lt;br&gt;
      ],&lt;br&gt;
      "fontFamilies": [&lt;br&gt;
        {&lt;br&gt;
          "slug": "body",&lt;br&gt;
          "fontFamily": "'Inter', sans-serif",&lt;br&gt;
          "name": "Inter"&lt;br&gt;
        }&lt;br&gt;
      ]&lt;br&gt;
    },&lt;br&gt;
    "spacing": {&lt;br&gt;
      "units": ["px", "rem", "em"],&lt;br&gt;
      "spacingScale": {&lt;br&gt;
        "steps": 4,&lt;br&gt;
        "increment": 0.5,&lt;br&gt;
        "unit": "rem"&lt;br&gt;
      }&lt;br&gt;
    },&lt;br&gt;
    "layout": {&lt;br&gt;
      "contentSize": "720px",&lt;br&gt;
      "wideSize": "1200px"&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcojdmavndncf3l905ci9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcojdmavndncf3l905ci9.png" alt="theme.json file open in code editor displaying typography settings and color palette design tokens in proper JSON format" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you define colors in theme.json, &lt;strong&gt;WordPress automatically generates CSS custom properties&lt;/strong&gt; for you. That primary color becomes &lt;code&gt;--wp--preset--color--primary&lt;/code&gt;. You can use it anywhere:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;css&lt;br&gt;
.custom-button {&lt;br&gt;
    background: var(--wp--preset--color--primary);&lt;br&gt;
    color: var(--wp--preset--color--background);&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The same applies to font sizes and spacing. Define them once in theme.json, use them everywhere through generated CSS variables. No manual coordination needed. The editor sidebar shows these options automatically. Users can pick from your design system without breaking the visual consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;theme.json turns your design decisions into reusable tokens that propagate automatically across WordPress.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is how you scale a design system. When you need to change your primary color, you change one value in theme.json. Every block, every component, every template that uses &lt;code&gt;--wp--preset--color--primary&lt;/code&gt; updates instantly. No grep, no find-and-replace, no missed instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters for Consistency
&lt;/h3&gt;

&lt;p&gt;The block editor gives users a lot of power. They can change colors, adjust font sizes, and add custom spacing. Without theme.json, they get access to every color in the browser color picker. With theme.json, they get a curated palette that matches your brand. They can still customize, but within guardrails.&lt;/p&gt;

&lt;p&gt;This is especially important if you're handing off the site to a client. You don't want them choosing random shades of blue that clash with the brand. You want them picking from a defined palette. theme.json enforces that by limiting their options to the colors, font sizes, and spacing values you defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  How They Work Together
&lt;/h2&gt;

&lt;p&gt;Here's the mental model: &lt;strong&gt;functions.php loads the resources, theme.json defines the design system, and style.css applies the visual rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When WordPress renders a page, it follows this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;functions.php runs first.&lt;/strong&gt; It enqueues fonts, CSS files, JavaScript files, and registers theme supports. This sets up the environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;theme.json generates CSS custom properties.&lt;/strong&gt; WordPress reads your color palette, font sizes, and spacing scale, then outputs those as CSS variables in the page &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;style.css applies visual rules.&lt;/strong&gt; Your stylesheet uses the fonts that functions.php loaded and the CSS variables that theme.json generated to style every element on the page.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation keeps each layer focused on one responsibility. When you need to add a new font, you touch functions.php. When you need to adjust spacing, you touch style.css. When you need to add a new color to the palette, you touch theme.json.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F803ug4zfwy0rkevff5ln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F803ug4zfwy0rkevff5ln.png" alt="Architecture diagram showing three WordPress theme files: functions.php as loader, style.css as visual applicator, theme.json as design system" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Real-World Example
&lt;/h3&gt;

&lt;p&gt;Let's say you want to add a custom accent color and use it in blog article headings. Here's how you'd implement it across all three files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the color in theme.json&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "version": 2,&lt;br&gt;
  "settings": {&lt;br&gt;
    "color": {&lt;br&gt;
      "palette": [&lt;br&gt;
        {&lt;br&gt;
          "slug": "accent",&lt;br&gt;
          "color": "#ff6b35",&lt;br&gt;
          "name": "Accent"&lt;br&gt;
        }&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;WordPress now generates &lt;code&gt;--wp--preset--color--accent&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Use the color in style.css&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;css&lt;br&gt;
h2 {&lt;br&gt;
    color: var(--wp--preset--color--accent);&lt;br&gt;
    font-weight: 600;&lt;br&gt;
    margin-top: 2rem;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your h2 headings now use the accent color defined in theme.json.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: No changes needed in functions.php&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You didn't need to load anything new or register anything additional. The color token already exists in the system.&lt;/p&gt;

&lt;p&gt;This is the ideal workflow. Design changes happen in theme.json. Visual application happens in style.css. Functionality stays in functions.php.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Should Put Where
&lt;/h2&gt;

&lt;p&gt;Here's a reference checklist you can use when building or refactoring a WordPress theme:&lt;/p&gt;

&lt;h3&gt;
  
  
  Put in functions.php:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wp_enqueue_style()&lt;/code&gt; calls for fonts and CSS files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wp_enqueue_script()&lt;/code&gt; calls for JavaScript files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_theme_support()&lt;/code&gt; for features like custom logo, post thumbnails, title tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;register_nav_menus()&lt;/code&gt; for navigation menu locations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;register_sidebar()&lt;/code&gt; for widget areas&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add_action()&lt;/code&gt; and &lt;code&gt;add_filter()&lt;/code&gt; for custom logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;register_block_type()&lt;/code&gt; for custom blocks&lt;/li&gt;
&lt;li&gt;Custom shortcode definitions&lt;/li&gt;
&lt;li&gt;Reading time calculators, table of contents generators, and similar utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Put in style.css:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Font-family declarations&lt;/li&gt;
&lt;li&gt;Font sizes, weights, and line heights&lt;/li&gt;
&lt;li&gt;Color declarations for text, backgrounds, borders, and accents&lt;/li&gt;
&lt;li&gt;Margin and padding for spacing rhythm&lt;/li&gt;
&lt;li&gt;Flexbox and grid layout rules&lt;/li&gt;
&lt;li&gt;Code block background colors and syntax highlighting&lt;/li&gt;
&lt;li&gt;Pull quote borders and italic styles&lt;/li&gt;
&lt;li&gt;Table of contents indentation and numbering&lt;/li&gt;
&lt;li&gt;Hover states for interactive elements&lt;/li&gt;
&lt;li&gt;Media queries for responsive breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Put in theme.json:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Color palette definitions with semantic slugs&lt;/li&gt;
&lt;li&gt;Typography scale with named font sizes&lt;/li&gt;
&lt;li&gt;Spacing scale with consistent rem values&lt;/li&gt;
&lt;li&gt;Font family declarations for body and headings&lt;/li&gt;
&lt;li&gt;Layout constraints (content width, wide width)&lt;/li&gt;
&lt;li&gt;Block editor feature toggles (disable custom colors, lock font sizes)&lt;/li&gt;
&lt;li&gt;Custom block style variations&lt;/li&gt;
&lt;li&gt;Default link colors and button styles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you follow this structure, your theme becomes modular. You can hand off styling to a designer who only needs to touch style.css. You can onboard a developer who only needs to understand functions.php. You can update design tokens in theme.json without touching code.&lt;/p&gt;

&lt;p&gt;This is how professional WordPress themes scale. Not by cramming everything into one file, but by respecting the purpose of each file and keeping concerns separated. When you structure your themes this way, maintenance becomes predictable, handoffs become smooth, and design iterations don't break functionality.&lt;/p&gt;

&lt;p&gt;WordPress gives you three files for three jobs. Use them correctly, and your design system will scale with every new block, page, and brand update. Mix them up, and you'll spend more time debugging than building.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Stop Putting Everything in functions.php: The WordPress File Separation Guide&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;functions.php vs style.css vs theme.json: What Actually Belongs Where&lt;/li&gt;
&lt;li&gt;WordPress Theme Architecture: Separating Logic, Styles, and Design Tokens&lt;/li&gt;
&lt;li&gt;The Three-File WordPress Theme System That Prevents Technical Debt&lt;/li&gt;
&lt;li&gt;How to Structure WordPress Themes Using functions.php, style.css, and theme.json&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;stop-putting-everything-in-functions-php-wordpress-file-separation-guide&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, tutorial, beginners, wordpress&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Stop Paying for Bot Traffic: A Two-Layer Defense for Next.js apps</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 05:17:02 +0000</pubDate>
      <link>https://dev.to/julianneagu/stop-paying-for-bot-traffic-a-two-layer-defense-for-nextjs-apps-1f23</link>
      <guid>https://dev.to/julianneagu/stop-paying-for-bot-traffic-a-two-layer-defense-for-nextjs-apps-1f23</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Bot traffic isn't a fact of life  -  it's a leak in your infrastructure budget. A two-layer defense using &lt;code&gt;robots.txt&lt;/code&gt; and Next.js middleware blocks 60-70% of resource-draining scrapers while keeping the bots you actually want. This approach runs at the edge, costs nothing to implement, and protects both your hosting bill and your analytics accuracy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most founders assume bot traffic is part of the cost of running a website. That assumption is expensive. Bots hammer your endpoints, consume metered server resources, pollute your analytics, and inflate your hosting bill. If you're on Vercel, AWS, or any pay-per-request platform, this traffic literally costs money every time it hits your server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is what a bot attack looks like - a sudden, unnatural spike that distorts your real user activity.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ustsdlg5vu33232tgf6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ustsdlg5vu33232tgf6.jpg" alt="Analytics dashboard showing active users, event counts, new users, and a large spike in activity on July 21, 2026, highlighted as an anomaly." width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The standard advice is to add a &lt;code&gt;robots.txt&lt;/code&gt; file and hope for the best. That only works if bots respect it. Most scrapers don't. They read your file, note what you've marked off-limits, and scrape it anyway. The file is a suggestion, not a firewall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real solution is a two-layer defense:&lt;/strong&gt; a smart &lt;code&gt;robots.txt&lt;/code&gt; configuration that filters well-behaved bots, and Next.js middleware running at the edge that enforces the rules before traffic reaches your application. Combined, they block 60-70% of unwanted bot traffic while keeping legitimate crawlers and AI assistants you actually want.&lt;/p&gt;

&lt;p&gt;This isn't about locking out AI crawlers. It's about controlling who gets access and at what cost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyr512wou1czyjd7drny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmyr512wou1czyjd7drny.png" alt="Dashboard showing bot traffic metrics and filtering statistics with request patterns and cost analysis" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Three Kinds of Visitors
&lt;/h2&gt;

&lt;p&gt;Your website serves three categories of traffic. Real humans browsing your site. Legitimate bots that index your content for search engines or train helpful AI models. And scrapers that hammer your endpoints, steal your data, rotate IP addresses, and pretend to be browsers.&lt;/p&gt;

&lt;p&gt;The third category is the problem. These bots ignore polite requests. They consume server resources you're paying for. They pollute your analytics by inflating visitor counts with fake traffic, making conversion rates appear worse than they are. They probe for vulnerabilities. If you're running on metered hosting, every request from a scraper costs you a fraction of a cent. When scrapers make up 30-40% of your traffic, that adds up fast.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;60-70% of bot traffic can be filtered by a properly configured &lt;code&gt;robots.txt&lt;/code&gt;, but only because most low-effort scrapers follow basic rules to avoid detection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The remaining 30-40% requires enforcement. That's where middleware comes in. It runs before every request hits your application, inspects headers and user agents, and blocks anything suspicious. On Vercel, middleware runs at the edge, meaning blocked requests never consume your server resources. You're not paying to process traffic you don't want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Smart robots.txt Configuration
&lt;/h2&gt;

&lt;p&gt;Start with &lt;code&gt;robots.txt&lt;/code&gt;. This file sits at the root of your domain and tells compliant bots what they can access. The key is being explicit about who you allow and who you block. Most sites either leave this file empty or use a generic "allow all" rule. That's leaving money on the table.&lt;/p&gt;

&lt;p&gt;Here's a structure that works:&lt;/p&gt;

&lt;p&gt;``txt&lt;/p&gt;

&lt;h1&gt;
  
  
  AI training &amp;amp; assistants - welcome
&lt;/h1&gt;

&lt;p&gt;User-agent: GPTBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: ChatGPT-User&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: ClaudeBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: anthropic-ai&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: PerplexityBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: Google-Extended&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: CCBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;h1&gt;
  
  
  Search engines - welcome
&lt;/h1&gt;

&lt;p&gt;User-agent: Googlebot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: Bingbot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;h1&gt;
  
  
  Known scrapers - blocked
&lt;/h1&gt;

&lt;p&gt;User-agent: SemrushBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: AhrefsBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: MJ12bot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: DotBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: PetalBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;h1&gt;
  
  
  Default - be polite or leave
&lt;/h1&gt;

&lt;p&gt;User-agent: *&lt;br&gt;
Crawl-delay: 10&lt;br&gt;
Disallow: /api/&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;This makes three categories clear. AI assistants and search engines get full access. Known scrapers get nothing. Everything else gets throttled with a 10-second crawl delay and blocked from API routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The crawl delay is critical.&lt;/strong&gt; Aggressive bots hitting your site every few seconds will slow down or leave. Legitimate crawlers won't care about a 10-second pause between requests. This single line filters out bots that hammer your site hundreds of times per minute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea9hhe6lex0jz6nldhck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fea9hhe6lex0jz6nldhck.png" alt="Code editor displaying robots.txt file configuration with AI bots, search engines, and scraper blocking rules" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Disallow: /api/&lt;/code&gt; rule is equally important. API routes often trigger database queries, authentication checks, or external service calls. Bots crawling these endpoints waste expensive compute. Block them by default unless you're running a public API that needs discovery.&lt;/p&gt;

&lt;p&gt;This configuration takes five minutes to set up. Copy the template, adjust the bot names if you have specific concerns, and deploy it to your site's root directory. For a deeper dive into &lt;code&gt;robots.txt&lt;/code&gt; strategy and common mistakes, check out &lt;a href="https://visionvix.com/robots-txt-best-practices/" rel="noopener noreferrer"&gt;VisionVix's robots.txt best practices guide&lt;/a&gt;, which walks through edge cases and SEO implications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most low-effort scrapers follow basic robots.txt rules to avoid detection, making it effective against the majority of bad actors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This handles the 60-70% of bot traffic that follows rules. It won't stop determined attackers, but it filters the lazy majority. Now we enforce the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Middleware Enforcement at the Edge
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; is polite. Middleware is the bouncer. It runs before every request hits your application, inspects the user agent and headers, and blocks anything suspicious. If you're using Next.js on Vercel, middleware runs at the edge. Requests get filtered before they consume your server resources. You're not paying to process blocked traffic.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;middleware.ts&lt;/code&gt; at the root of your Next.js project:&lt;/p&gt;

&lt;p&gt;``typescript&lt;br&gt;
import { NextResponse } from 'next/server'&lt;/p&gt;

&lt;p&gt;const ALLOWED_BOTS = [&lt;br&gt;
  'googlebot',&lt;br&gt;
  'bingbot',&lt;br&gt;
  'gptbot',&lt;br&gt;
  'claudebot',&lt;br&gt;
  'perplexitybot',&lt;br&gt;
  'slackbot',&lt;br&gt;
  'twitterbot'&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;const BLOCKED_BOTS = [&lt;br&gt;
  'petalbot',&lt;br&gt;
  'blexbot',&lt;br&gt;
  'semrushbot',&lt;br&gt;
  'ahrefsbot',&lt;br&gt;
  'mj12bot',&lt;br&gt;
  'dotbot'&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;const SCRAPER_LIBS = [&lt;br&gt;
  'scrapy',&lt;br&gt;
  'python-requests',&lt;br&gt;
  'curl/',&lt;br&gt;
  'wget',&lt;br&gt;
  'java/',&lt;br&gt;
  'go-http-client'&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;export function middleware(req) {&lt;br&gt;
  const ua = (req.headers.get('user-agent') || '').toLowerCase()&lt;/p&gt;

&lt;p&gt;// 1. Allow good bots&lt;br&gt;
  if (ALLOWED_BOTS.some(bot =&amp;gt; ua.includes(bot))) {&lt;br&gt;
    return NextResponse.next()&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 2. Block empty user agents&lt;br&gt;
  if (!req.headers.get('user-agent')) {&lt;br&gt;
    return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 3. Block aggressive crawlers&lt;br&gt;
  if (BLOCKED_BOTS.some(bot =&amp;gt; ua.includes(bot))) {&lt;br&gt;
    return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 4. Block scraper libraries&lt;br&gt;
  if (SCRAPER_LIBS.some(lib =&amp;gt; ua.includes(lib))) {&lt;br&gt;
    return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// 5. Block missing Accept header&lt;br&gt;
  if (!req.headers.get('accept')) {&lt;br&gt;
    return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Default: pass&lt;br&gt;
  return NextResponse.next()&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;Every request triggers this function. The middleware reads the user agent string and headers. If the user agent matches a known good bot, the request passes immediately. If it matches a blocked bot or scraper library, it gets a 403 response. If critical headers are missing, it's blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Accept header check is subtle but effective.&lt;/strong&gt; Real browsers and legitimate bots send an &lt;code&gt;Accept&lt;/code&gt; header telling the server what content types they understand. Scrapers often skip this. Blocking requests without an &lt;code&gt;Accept&lt;/code&gt; header catches a lot of low-effort scraping attempts.&lt;/p&gt;

&lt;p&gt;Middleware runs at the Vercel edge, meaning blocked requests never hit your application server or consume compute resources. This is what reduces the remaining 30-40% of bot traffic that ignored &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Gets Blocked and Why
&lt;/h2&gt;

&lt;p&gt;Let's walk through what each check catches in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Empty User Agents
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;br&gt;
if (!req.headers.get('user-agent')) {&lt;br&gt;
  return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Legitimate browsers always send a user agent. Tools like &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;wget&lt;/code&gt; do too, but scrapers built with low-effort scripts often don't. This single check blocks a surprising amount of junk traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scraper Libraries
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;br&gt;
if (SCRAPER_LIBS.some(lib =&amp;gt; ua.includes(lib))) {&lt;br&gt;
  return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This targets common scraping tools. A user agent string like &lt;code&gt;python-requests/2.28.0&lt;/code&gt; or &lt;code&gt;Scrapy/2.9.0&lt;/code&gt; reveals exactly what's hitting your site. These are almost never legitimate traffic. Block them immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing Accept Header
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;typescript&lt;br&gt;
if (!req.headers.get('accept')) {&lt;br&gt;
  return new NextResponse('Forbidden', { status: 403 })&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Real browsers send an &lt;code&gt;Accept&lt;/code&gt; header like &lt;code&gt;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&lt;/code&gt;. Bots that don't care about the response format skip this header. It's a strong signal that the request isn't coming from a browser or a well-behaved crawler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9o3edpo4wip6ggqzhjzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9o3edpo4wip6ggqzhjzc.png" alt="Next.js middleware code implementation showing user agent detection and bot blocking logic" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conditional Middleware Pattern
&lt;/h2&gt;

&lt;p&gt;Not every route needs the same level of protection. Public pages like your homepage or blog should allow all legitimate traffic. Admin panels, API routes, or expensive database queries need stricter rules.&lt;/p&gt;

&lt;p&gt;You can add conditional logic to your middleware:&lt;/p&gt;

&lt;p&gt;``typescript&lt;br&gt;
export function middleware(req) {&lt;br&gt;
  const pathname = req.nextUrl.pathname&lt;/p&gt;

&lt;p&gt;// Skip middleware for static assets&lt;br&gt;
  if (pathname.startsWith('/_next') || pathname.startsWith('/static')) {&lt;br&gt;
    return NextResponse.next()&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Apply strict rules to API routes&lt;br&gt;
  if (pathname.startsWith('/api')) {&lt;br&gt;
    return strictBotCheck(req)&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Apply normal rules to everything else&lt;br&gt;
  return normalBotCheck(req)&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;This pattern lets you enforce stricter checks on expensive routes while keeping public pages accessible. The &lt;code&gt;strictBotCheck&lt;/code&gt; function could block all bots except explicitly allowed ones. The &lt;code&gt;normalBotCheck&lt;/code&gt; function could allow most traffic but throttle suspicious patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honeypot Traps and Advanced Tactics
&lt;/h2&gt;

&lt;p&gt;If you want to go further, add a honeypot trap. Create a page that's linked in your &lt;code&gt;robots.txt&lt;/code&gt; under a &lt;code&gt;Disallow&lt;/code&gt; rule. Good bots won't visit it. Bad bots will.&lt;/p&gt;

&lt;p&gt;Add this to your &lt;code&gt;robots.txt&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;txt&lt;br&gt;
User-agent: *&lt;br&gt;
Disallow: /trap&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then create a &lt;code&gt;/trap&lt;/code&gt; route in your Next.js app. Anyone who visits it gets their IP logged and blocked:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;typescript&lt;br&gt;
// pages/api/trap.ts&lt;br&gt;
export default function handler(req, res) {&lt;br&gt;
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress&lt;br&gt;
  console.log(&lt;/code&gt;Bot trap triggered by IP: ${ip}`)&lt;/p&gt;

&lt;p&gt;// Add IP to blocklist&lt;br&gt;
  // (integrate with your edge config or database)&lt;/p&gt;

&lt;p&gt;return res.status(403).json({ error: 'Forbidden' })&lt;br&gt;
}&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;This catches bots that explicitly ignore your &lt;code&gt;robots.txt&lt;/code&gt; rules. You can feed these IPs into your middleware blocklist or configure edge rules at the CDN level. For a broader look at proactive security measures like this, see the &lt;a href="https://securityaudit.dev/blog/website-security-scanning" rel="noopener noreferrer"&gt;website security scanning guide&lt;/a&gt;, which covers automated vulnerability detection and bot behavior analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Actually Protecting
&lt;/h2&gt;

&lt;p&gt;This defense isn't just about saving money. It protects three things founders underestimate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting costs.&lt;/strong&gt; If bots make up 30-40% of your traffic, you're paying 30-40% more than you should. On metered platforms like Vercel or AWS Lambda, every function invocation costs money. Blocking bot traffic before it hits your application cuts those costs immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics accuracy.&lt;/strong&gt; Bot traffic inflates your visitor counts and skews conversion rates. If 40% of your "users" are scrapers, your real conversion rate is higher than your dashboard shows. Clean traffic gives you accurate data to make decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attack surface.&lt;/strong&gt; Bots probe for vulnerabilities. They test SQL injection patterns, brute-force login endpoints, and enumerate API routes. Blocking them reduces your exposure to automated attacks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Blocking requests without Accept headers or empty user agents catches most low-effort scraping attempts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci3m5wnx4j1d7hqgtlma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci3m5wnx4j1d7hqgtlma.png" alt="Analytics comparison showing before and after metrics of bot filtering implementation on traffic costs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Checklist
&lt;/h2&gt;

&lt;p&gt;Here's what to do right now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy the &lt;code&gt;robots.txt&lt;/code&gt; template&lt;/strong&gt; to your site's root directory. Adjust the allowed and blocked bot lists based on your traffic logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the middleware file&lt;/strong&gt; to your Next.js project. Copy the full code block above, save it as &lt;code&gt;middleware.ts&lt;/code&gt; at the project root, and deploy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor your logs&lt;/strong&gt; for a week. Check which user agents are getting blocked. If you see legitimate traffic being filtered, adjust your &lt;code&gt;ALLOWED_BOTS&lt;/code&gt; list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review your hosting bill&lt;/strong&gt; after 30 days. If bots were a significant portion of your traffic, you should see a measurable drop in compute costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check your analytics&lt;/strong&gt; for cleaner data. Visitor counts should drop, but conversion rates should improve because you're measuring real users instead of bots.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This setup takes less than an hour to implement. It costs nothing. It runs at the edge, so there's no performance penalty for legitimate users. And it blocks the majority of bot traffic that's wasting your budget and polluting your metrics.&lt;/p&gt;

&lt;p&gt;If you've been treating bot traffic as inevitable, stop. It's not. Two layers of defense, five minutes of configuration, and you're done. The bots you want still get in. The ones you don't pay for anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Stop Paying for Bot Traffic: A Two-Layer Defense for Next.js Apps&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How robots.txt + Next.js Middleware Cuts Bot Costs by 60-70%&lt;/li&gt;
&lt;li&gt;Block Scrapers, Keep AI Crawlers: Smart Bot Management for Next.js&lt;/li&gt;
&lt;li&gt;The $500/Month Bot Problem (And How to Fix It With Edge Middleware)&lt;/li&gt;
&lt;li&gt;robots.txt Isn't Enough: Building a Real Bot Firewall in Next.js&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;stop-paying-for-bot-traffic-nextjs-middleware-defense&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;nextjs, webdev, devops, security&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Blocking GPTBot Won't Remove You From ChatGPT Search Here's Why</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:44:19 +0000</pubDate>
      <link>https://dev.to/julianneagu/blocking-gptbot-wont-remove-you-from-chatgpt-search-heres-why-30m1</link>
      <guid>https://dev.to/julianneagu/blocking-gptbot-wont-remove-you-from-chatgpt-search-heres-why-30m1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Blocking GPTBot doesn't remove you from ChatGPT search results. OpenAI runs two separate bots: one that trains models (GPTBot) and one that fetches live citations (OAI-SearchBot). Every major AI company uses this dual-bot system. Block the wrong one and you vanish from AI search. Block the right one and you protect your content while staying visible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most people who block GPTBot expect their content to disappear from ChatGPT entirely. Then they search for their own article title in ChatGPT and find it sitting right there in the results with a working link and an accurate pull quote. The block didn't fail. You just blocked the wrong bot.&lt;/p&gt;

&lt;p&gt;OpenAI doesn't run one bot. They run two. So does Anthropic. So does Google. Every major AI company operates a two-bot system because training and citation are fundamentally different jobs that need fundamentally different infrastructure.&lt;/p&gt;

&lt;p&gt;The first bot harvests your content to train the model. It reads your articles, extracts patterns from your prose, and bakes those patterns into billions of model parameters. Your words become part of the AI's permanent knowledge. The model never cites you. It never links back to you. It never sends you traffic. &lt;strong&gt;Training bots give you nothing in return for what they take.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The second bot fetches your content in real time when someone asks a question your site might answer. The bot finds your page, reads the relevant section, and surfaces it in the AI's response with your name, a link, and a snippet. This is how you show up in ChatGPT search results. &lt;strong&gt;Citation bots drive referral traffic and keep you visible in AI-powered search.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpehesxi13okccfft937.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpehesxi13okccfft937.png" alt="Diagram showing two separate bots: training bot crawling content for model parameters versus citation bot fetching live pages for search results" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blocking the training bot does absolutely nothing to the citation bot. They don't share a blocklist. They don't check each other's permissions. They operate as if they work for separate companies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two-Bot System Every AI Company Runs
&lt;/h2&gt;

&lt;p&gt;Here's the full breakdown of how each major AI company splits training from citation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI&lt;/strong&gt; runs GPTBot for training. GPTBot crawls your site, reads your content, and uses it to improve future models. That's where your IP goes in and never comes back out. For live citations, OpenAI runs OAI-SearchBot and ChatGPT-User. These bots fetch content when a user asks a question. Block GPTBot and you stop training. Block OAI-SearchBot and you vanish from ChatGPT search results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic&lt;/strong&gt; operates ClaudeBot for training. ClaudeBot works exactly like GPTBot. It harvests content to build the model's knowledge base. For citations, Anthropic runs Claude-SearchBot and Claude-User. Block ClaudeBot to stop training. Leave Claude-SearchBot allowed if you want to appear in Claude's search answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google&lt;/strong&gt; uses Google-Extended for training. This is separate from Googlebot, which has been crawling the web for decades. Googlebot handles citations because Google already had a citation infrastructure before AI search existed. Block Google-Extended and you stop contributing to Gemini training. Block Googlebot and you disappear from Google Search entirely, which most sites can't afford.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Perplexity&lt;/strong&gt; runs PerplexityBot and Perplexity-User. Both fetch content for live answers. Perplexity doesn't train foundation models, so they don't operate a separate training crawler. Every request from Perplexity is a citation request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meta&lt;/strong&gt; operates meta-externalagent for training. They don't currently run a public citation bot for consumer search products, so blocking meta-externalagent has no downside for visibility.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Training crawlers need bulk access to millions of pages, running batch jobs that might revisit your content weeks or months later. Citation crawlers need low-latency access to fresh content, often fetching a single page in response to a specific user query.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This architecture exists because training and citation have fundamentally different requirements. Training bots run in batches. They might crawl your site once and never come back. They don't care about freshness. They care about volume. Citation bots run on demand. They fetch a single page in milliseconds when a user asks a question. They care about speed and freshness, not volume.&lt;/p&gt;

&lt;p&gt;Building one bot that does both jobs well is harder than running two specialized systems. So every company runs two. As &lt;a href="https://aiagentready.dev/blog/ai-crawling-vs-traditional-crawling-explained" rel="noopener noreferrer"&gt;AI crawling vs traditional crawling&lt;/a&gt; explains, the infrastructure behind these systems diverges at nearly every layer, from request patterns to caching strategies to how they handle rate limits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1krmreyie66c4wmlu64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1krmreyie66c4wmlu64.png" alt="Comparison chart of training bot batch operations versus citation bot real-time, on-demand request patterns and infrastructure requirements" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Blocking Right Now (And What You're Not)
&lt;/h2&gt;

&lt;p&gt;Most robots.txt files I see block training bots and stay silent on citation bots. That's not necessarily wrong. The robots.txt standard works by explicit permission. If you don't mention a bot, it's allowed by default. But I don't like relying on default behavior. Defaults change when companies push updates or rewrite their crawler logic.&lt;/p&gt;

&lt;p&gt;Here's what a safe, explicit robots.txt configuration looks like if you want to block training but allow citations:&lt;/p&gt;

&lt;p&gt;``txt&lt;br&gt;
User-agent: GPTBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: ClaudeBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: CCBot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: Google-Extended&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: Bytespider&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: Amazonbot&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: meta-externalagent&lt;br&gt;
Disallow: /&lt;/p&gt;

&lt;p&gt;User-agent: OAI-SearchBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: ChatGPT-User&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: Claude-SearchBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: Claude-User&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: PerplexityBot&lt;br&gt;
Allow: /&lt;/p&gt;

&lt;p&gt;User-agent: Perplexity-User&lt;br&gt;
Allow: /&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;The first block stops training. The second block guarantees you stay citable. Both blocks matter. Leaving citation bots unstated means trusting default behavior, and I've seen too many cases where defaults shifted after a company updated their crawler documentation.&lt;/p&gt;

&lt;p&gt;You'll notice Googlebot isn't in either list. That's intentional. Blocking Googlebot means disappearing from Google Search entirely, which most sites can't survive. Google-Extended is the training-specific bot. Block that and leave Googlebot alone. You get the split you want: no training, yes citations.&lt;/p&gt;

&lt;p&gt;One more detail. If you're running a Cloudflare-managed site, your robots.txt likely includes a comment that says &lt;code&gt;# BEGIN Cloudflare Managed content&lt;/code&gt;. That marker means Cloudflare auto-generates part of your robots.txt based on dashboard settings. &lt;strong&gt;Don't edit robots.txt directly when that marker is present.&lt;/strong&gt; Cloudflare will overwrite your changes on the next sync. Make changes through Cloudflare's AI Crawl Control panel instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Edit Robots.txt When Cloudflare Manages It
&lt;/h2&gt;

&lt;p&gt;If you see the Cloudflare marker in your robots.txt, here's the correct path to making changes stick:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into your Cloudflare dashboard and select the domain you're managing.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Domains&lt;/strong&gt; in the left sidebar.&lt;/li&gt;
&lt;li&gt;Select your specific domain from the list.&lt;/li&gt;
&lt;li&gt;Scroll down to &lt;strong&gt;AI Crawl Control&lt;/strong&gt; or search for "AI" in the page search.&lt;/li&gt;
&lt;li&gt;Toggle the bots you want to block or allow using the switches provided.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you've been blocking GPTBot and wondering why your articles still show up in ChatGPT, now you know. The bot you blocked wasn't the bot you thought it was. And if you want to stay visible in AI search while protecting your content from training, you need to configure both. For a deeper look at how AI systems decide what to crawl and when, see this &lt;a href="https://visionvix.com/claude-vs-gpt-for-coding-comparison-and-reviews/" rel="noopener noreferrer"&gt;comparison of Claude vs GPT for coding tasks&lt;/a&gt;, which shows how different models prioritize different types of content even when using similar underlying architectures.&lt;/p&gt;

&lt;p&gt;Cloudflare's AI Crawl Control panel handles the syntax for you. Changes take effect within a few minutes and won't be overwritten by automated syncs. If you need more granular control than Cloudflare's toggles provide, you'll need to disable Cloudflare management of robots.txt entirely and manage the file manually as we advance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzumungy45vr3cn9upfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzumungy45vr3cn9upfp.png" alt="Cloudflare dashboard showing AI training bots (GPTBot, ClaudeBot, CCBot) all registering zero requests, confirming blocks work" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking Whether Your Block Actually Works
&lt;/h2&gt;

&lt;p&gt;After updating your robots.txt, wait 24 hours and check your server logs or analytics dashboard. Look for requests from the bots you tried to block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training bots&lt;/strong&gt; like GPTBot, ClaudeBot, and Google-Extended should show zero requests and zero bytes transferred after blocking. If you still see requests from GPTBot after a week, either your robots.txt syntax is wrong or the bot isn't respecting the directive. Cloudflare's WAF lets you write a firewall rule that blocks by user agent string if robots.txt alone doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citation bots&lt;/strong&gt; like OAI-SearchBot and ChatGPT-User should show periodic requests when users ask questions your content might answer. These requests are low-volume and sporadic. You might see a handful per week for a niche topic or dozens per day if you rank well for common queries. This is normal and desirable. Each request represents a potential citation in an AI answer.&lt;/p&gt;

&lt;p&gt;If you're not seeing any citation bot traffic after allowing them, check two things. First, make sure your &lt;code&gt;Allow: /&lt;/code&gt; directive is present and correctly formatted. Second, verify that your content actually ranks for questions people ask AI tools. Not every page earns citations. The citation bots only fetch pages that match a user query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More Than You Think
&lt;/h2&gt;

&lt;p&gt;Training without attribution is economically asymmetric. Your content becomes part of a billion-dollar model's knowledge base. You get no traffic, no links, and no royalties. The model answers questions using patterns it learned from your prose, and you never see a visitor.&lt;/p&gt;

&lt;p&gt;Citation with attribution reverses that asymmetry. Your content appears as a source. The AI links to your page. Users click through. You get referral traffic from people who were already looking for what you wrote about. &lt;strong&gt;This is the only version of AI-powered search that benefits content creators.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blocking training bots while allowing citation bots is the rational default for most sites. You protect your IP from being absorbed into the model's weights. You stay visible in the AI-powered search results that are increasingly replacing traditional Google queries. You get the upside without the downside.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpneihyxxsdklb7jjq1t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpneihyxxsdklb7jjq1t.png" alt="Table showing AI companies' bot pairs: training bots (GPTBot, ClaudeBot) vs citation bots (OAI-SearchBot, Claude-SearchBot)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The mistake most people make is thinking GPTBot controls all of OpenAI's access to your site. It doesn't. GPTBot controls training. OAI-SearchBot controls citations. Block one, allow the other, and you get the outcome you actually want.&lt;/p&gt;

&lt;p&gt;The two-bot system isn't going away. Every AI company has adopted it because the engineering requirements for training and citation are incompatible. One bot can't do both jobs well. So they split the work. And you need to split your robots.txt configuration to match.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Blocking GPTBot Won't Remove You From ChatGPT Search — Here's Why&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You're Blocking the Wrong AI Bot: The Training vs Citation Crawler Problem&lt;/li&gt;
&lt;li&gt;Why Your Site Still Appears in ChatGPT After Blocking GPTBot&lt;/li&gt;
&lt;li&gt;The Two-Bot System: How AI Companies Split Training From Citations&lt;/li&gt;
&lt;li&gt;Block GPTBot, Still Show Up in ChatGPT: Understanding AI Crawler Architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;blocking-gptbot-wont-remove-you-from-chatgpt-search&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, ai, opensource, seo&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Auto-Submit Changed Pages to Search Engines with GitHub Actions and IndexNow</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Wed, 29 Jul 2026 13:50:20 +0000</pubDate>
      <link>https://dev.to/julianneagu/auto-submit-changed-pages-to-search-engines-with-github-actions-and-indexnow-3fgo</link>
      <guid>https://dev.to/julianneagu/auto-submit-changed-pages-to-search-engines-with-github-actions-and-indexnow-3fgo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you're pushing updates to a static site every week, manually notifying search engines is a waste of time. GitHub Actions can detect exactly which files changed in each commit, convert those paths to URLs, and submit them to IndexNow automatically. You build once, push to main, and search engines get notified within minutes - no batch uploads, no forgotten submissions, no resubmitting 388 URLs when only three changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your static site gets a dozen updates a week, manually notifying search engines gets old fast. Most builders either skip the submissions entirely or batch them monthly, which means new pages sit invisible for weeks.&lt;/p&gt;

&lt;p&gt;GitHub Actions combined with IndexNow solves this. Your repo already knows what changed, and search engines support instant notifications. You just need to wire them together.&lt;/p&gt;

&lt;p&gt;Here's the real difference: instead of pushing code and then remembering to notify Bing or Yandex later, you build a workflow that detects changed files, waits for your deployment to finish, and submits only the modified URLs. No manual script runs. No forgotten submissions. No resubmitting all 388 URLs when only three changed.&lt;/p&gt;

&lt;p&gt;This article walks through what GitHub Actions does, how IndexNow works, and how to set up submissions that fire on every push to main.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkx83gvxs8kl5dhho0fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkx83gvxs8kl5dhho0fz.png" alt="Diagram showing the automated workflow from code push to search engine notification via GitHub Actions and IndexNow" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What GitHub Actions Actually Does
&lt;/h2&gt;

&lt;p&gt;GitHub Actions is GitHub's built-in automation system. You write a workflow file, drop it in &lt;code&gt;.github/workflows/&lt;/code&gt;, and GitHub runs it whenever a trigger fires. Common triggers include pushing code, opening a pull request, or hitting a cron schedule.&lt;/p&gt;

&lt;p&gt;For static sites and micro-tools, this becomes a deployment and notification engine. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a build step when you push to main&lt;/li&gt;
&lt;li&gt;Detect which files changed in the commit&lt;/li&gt;
&lt;li&gt;Convert those file paths into live URLs&lt;/li&gt;
&lt;li&gt;Wait for your hosting platform to finish deploying&lt;/li&gt;
&lt;li&gt;Submit only the changed URLs to search engines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key advantage: GitHub already has the commit diff. It knows exactly which pages you modified. You don't need to scan your entire sitemap or keep a separate change log.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run on Every Push
&lt;/h3&gt;

&lt;p&gt;The most common trigger is push to a specific branch:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yaml&lt;br&gt;
on:&lt;br&gt;
  push:&lt;br&gt;
    branches:&lt;br&gt;
      - main&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every time you merge a PR or push directly to main, the workflow fires. This matches how most teams deploy: main is production, and every merge should trigger a deployment notification.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvcx1tyhm6rxq6kli998.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvcx1tyhm6rxq6kli998.png" alt="GitHub Actions workflow trigger configuration showing push events on main branch" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For teams managing multiple repositories with similar automation needs, &lt;a href="https://visionvix.com/github-repository-best-practices/" rel="noopener noreferrer"&gt;GitHub repository best practices&lt;/a&gt; covers how to structure &lt;code&gt;.github/&lt;/code&gt; folders, share workflows across repos, and maintain consistency when scaling to dozens of projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detect Changed Files
&lt;/h3&gt;

&lt;p&gt;GitHub Actions can extract the list of modified files from the commit diff. The official IndexNow Action supports multiple input modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;urls-from&lt;/strong&gt;: generate URLs from a shell command (like parsing the diff)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;file&lt;/strong&gt;: read URLs from a text file you generate in an earlier step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sitemap&lt;/strong&gt;: submit URLs from an existing XML sitemap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;urls&lt;/strong&gt;: pass a direct list of URLs as a string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For changed-file workflows, &lt;code&gt;urls-from&lt;/code&gt; is cleanest. You run a command that outputs one URL per line, and the Action reads that output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fau3fjgtr1cnm5aletxda.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fau3fjgtr1cnm5aletxda.png" alt="Terminal output displaying git diff command filtering modified HTML files and converting them to URLs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example shell logic on macOS or Linux:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bash&lt;br&gt;
git diff-tree --no-commit-id --name-only -r HEAD | \&lt;br&gt;
  grep 'index\.html$' | \&lt;br&gt;
  sed 's|^|https://yourdomain.com/|' | \&lt;br&gt;
  sed 's|/index\.html$|/|'&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This grabs modified files, filters for &lt;code&gt;index.html&lt;/code&gt; entries (assuming each page is a folder with an index file), and converts paths into full URLs.&lt;/p&gt;

&lt;p&gt;On Windows with PowerShell, you'd use native cmdlets instead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;powershell&lt;br&gt;
$changedFiles = git diff-tree --no-commit-id --name-only -r HEAD&lt;br&gt;
$urls = $changedFiles | Where-Object { $_ -like '*index.html' } | ForEach-Object {&lt;br&gt;
  $_ -replace '^', 'https://yourdomain.com/' -replace '/index\.html$', '/'&lt;br&gt;
}&lt;br&gt;
$urls -join "`n"&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Both produce the same output: a list of URLs, one per line, ready to feed into the IndexNow Action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submit URLs to IndexNow Automatically
&lt;/h2&gt;

&lt;p&gt;The IndexNow Action handles the actual submission. It supports five search engines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bing&lt;/li&gt;
&lt;li&gt;Yandex&lt;/li&gt;
&lt;li&gt;Naver&lt;/li&gt;
&lt;li&gt;Seznam&lt;/li&gt;
&lt;li&gt;Yep&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;IndexNow submissions are shared across all supporting engines. You submit once, and Bing tells Yandex, Naver, Seznam, and Yep about the update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxzvp7we3t6ojlo7ovfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxzvp7we3t6ojlo7ovfi.png" alt="IndexNow logo with icons representing the five supported search engines: Bing, Yandex, Naver, Seznam, and Yep" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Action supports multiple feed formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XML sitemaps&lt;/li&gt;
&lt;li&gt;RSS feeds&lt;/li&gt;
&lt;li&gt;Atom feeds&lt;/li&gt;
&lt;li&gt;Direct URL lists&lt;/li&gt;
&lt;li&gt;Custom endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For changed-file workflows, direct URL lists are simplest. You pass the output of your diff command and let the Action batch the submissions.&lt;/p&gt;

&lt;p&gt;If you're building workflows for rapid deployment and want to see how to structure automation logic around changed files and deployment hooks, check out &lt;a href="https://workflowgenerator.dev/blog/building-your-first-automated-workflow-step" rel="noopener noreferrer"&gt;building your first automated workflow step&lt;/a&gt; for a deeper dive into event-driven automation patterns.&lt;/p&gt;

&lt;p&gt;The Action also supports limits (submit only the first N URLs), time filtering (submit only URLs modified since X days ago), and custom key file locations.&lt;/p&gt;

&lt;p&gt;Example workflow snippet:&lt;/p&gt;

&lt;p&gt;``yaml&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: Submit changed URLs to IndexNow
uses: bojieyang/indexnow-action@v2
with:
sitemap-location: '&lt;a href="https://yourdomain.com/sitemap.xml" rel="noopener noreferrer"&gt;https://yourdomain.com/sitemap.xml&lt;/a&gt;'
key: ${{ secrets.INDEXNOW_KEY }}
key-location: '&lt;a href="https://yourdomain.com/indexnow-key.txt" rel="noopener noreferrer"&gt;https://yourdomain.com/indexnow-key.txt&lt;/a&gt;'
``&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This submits URLs from your sitemap. For changed-file workflows, you'd replace &lt;code&gt;sitemap-location&lt;/code&gt; with &lt;code&gt;urls-from&lt;/code&gt; and pass your diff command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53noegonn5o89denyqkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53noegonn5o89denyqkf.png" alt="Code snippet showing the IndexNow Action configuration with sitemap location and API key parameters" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run on a Schedule
&lt;/h3&gt;

&lt;p&gt;You can also trigger workflows on a cron schedule:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yaml&lt;br&gt;
on:&lt;br&gt;
  schedule:&lt;br&gt;
    - cron: '0 2 * * *'  # 2 AM UTC daily&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is useful for submitting your full sitemap once a day, even if nothing changed. Some builders use this as a safety net: changed-file submissions run on every push, and a daily full-sitemap submission catches anything the diff logic missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Secrets for Keys
&lt;/h2&gt;

&lt;p&gt;GitHub Actions supports encrypted secrets. You store your IndexNow key in repository settings under &lt;strong&gt;Secrets and variables&lt;/strong&gt;, then reference it in your workflow as &lt;code&gt;${{ secrets.INDEXNOW_KEY }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This keeps the key out of your public repository. The workflow file itself is visible, but the key value is encrypted and only decrypted during workflow runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fw317x1t0jeeuk0tg15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fw317x1t0jeeuk0tg15.png" alt="Visual comparison between manual URL submission workflow and automated GitHub Actions submission process" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Key Files Dynamically
&lt;/h3&gt;

&lt;p&gt;The official IndexNow docs warn against committing your key file to a public repository. If the file is visible in your repo, anyone can see the key contents.&lt;/p&gt;

&lt;p&gt;The recommended approach: generate the key file during deployment. Your hosting platform writes the key to disk as part of the build step, so it's available at &lt;code&gt;https://yourdomain.com/indexnow-key.txt&lt;/code&gt; but never committed to version control.&lt;/p&gt;

&lt;p&gt;Most static site hosts support build hooks or environment variable injection. You can write a simple script that creates the key file from a secret environment variable during the build process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A typical static site might have 388 URLs total but only 3 changed URLs per update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this workflow in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every push to main triggers the workflow&lt;/li&gt;
&lt;li&gt;GitHub extracts the diff and converts changed paths to URLs&lt;/li&gt;
&lt;li&gt;The IndexNow Action submits only those URLs to all five supported search engines&lt;/li&gt;
&lt;li&gt;Search engines crawl the updated pages within hours instead of weeks&lt;/li&gt;
&lt;li&gt;You never manually submit a URL again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional sitemap-based indexing can take days or weeks. IndexNow reduces this to hours. Sites that receive a dozen updates per week benefit the most, because each batch of changes gets notified immediately instead of sitting in a queue until someone remembers to submit them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2j0ofs5sd7uttxllu71p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2j0ofs5sd7uttxllu71p.png" alt="GitHub Actions workflow status dashboard showing successful deployment and IndexNow submission steps" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Submitting the entire sitemap on every push.&lt;/strong&gt; This works, but it wastes API quota and makes it harder for search engines to prioritize your fresh content. Only submit what changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to wait for deployment.&lt;/strong&gt; GitHub Actions can't detect deployment completion automatically. If your workflow submits URLs before your hosting platform finishes deploying, search engines will crawl stale versions. Add a delay step or poll your deployment status endpoint before submitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Committing your IndexNow key to a public repo.&lt;/strong&gt; Use GitHub Secrets and generate the key file dynamically during deployment. Never hardcode the key in a workflow file or commit it to version control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assuming Google supports IndexNow.&lt;/strong&gt; Google does not. You need a separate notification mechanism, either through the Search Console API or by updating your XML sitemap and pinging Google's submission endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Static Sites
&lt;/h2&gt;

&lt;p&gt;Static sites change frequently in small batches rather than all at once. You publish a new blog post, fix a typo on three pages, add a new product page. Each of these events is a small diff.&lt;/p&gt;

&lt;p&gt;Most static site builds finish in under 60 seconds. That means your workflow can detect changes, wait for deployment, and submit URLs within two minutes of pushing to main.&lt;/p&gt;

&lt;p&gt;This kind of speed doesn't matter for sites that update once a month. But if you're shipping updates every few days, the difference between manual batch submissions and automatic change detection is the difference between pages that index in hours and pages that sit invisible for weeks.&lt;/p&gt;

&lt;p&gt;You build the workflow once. After that, every push to main handles its own search engine notifications. No manual steps. No forgotten submissions. No wasted API quota on unchanged URLs.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Auto-Submit Changed Pages to Search Engines with GitHub Actions and IndexNow&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop Manual Search Submissions: Automate IndexNow with GitHub Actions&lt;/li&gt;
&lt;li&gt;GitHub Actions + IndexNow: Submit Only Changed URLs to Search Engines&lt;/li&gt;
&lt;li&gt;How to Auto-Notify Search Engines When You Push Static Site Updates&lt;/li&gt;
&lt;li&gt;Automate Search Engine Notifications for Static Sites with GitHub Actions&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;auto-submit-changed-pages-search-engines-github-actions-indexnow&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, devops, tutorial, indexnow&lt;/p&gt;

</description>
      <category>indexnow</category>
      <category>tutorial</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>We Built 1,500 Keyword-Rich Domains Before Chasing Backlinks, Here's What Happened</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Mon, 27 Jul 2026 14:20:50 +0000</pubDate>
      <link>https://dev.to/julianneagu/we-built-1500-keyword-rich-domains-before-chasing-backlinks-heres-what-happened-4h2e</link>
      <guid>https://dev.to/julianneagu/we-built-1500-keyword-rich-domains-before-chasing-backlinks-heres-what-happened-4h2e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We built 1,500+ keyword-rich domains hosting real AI tools before worrying about backlinks. Each domain is a production-grade product that passes enterprise security audits. The result: over 1 million AI citations in seven months, impressions within days of launch, and a compounding authority mesh that grows sideways rather than up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most founders obsess over backlinks. They build a product, launch it on a generic domain, then spend months begging for links from high-authority sites. We flipped that model.&lt;/p&gt;

&lt;p&gt;We built the ranking infrastructure first. Then we filled it with products.&lt;/p&gt;

&lt;p&gt;What started as a bootstrapped need for tools we couldn't afford became a 1,500-domain ecosystem. Every domain hosts a real AI tool. Every tool solves a real problem. Every site passes the strictest compliance audits from day one. This isn't a private blog network where hollow sites exist only to pass link juice. It's a distributed product mesh that compounds authority through authentic user engagement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmnyz4drcezey34igloo7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmnyz4drcezey34igloo7.jpeg" alt="Line graph showing Citations and Cited Pages metrics from December to June, with Citations in purple and Cited Pages in blue&lt;br&gt;
" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In seven months, this network-mesh approach drove more than one million AI citations to VisionVix.com. That number isn't a vanity metric. It's a massive indicator of how fast the ecosystem compounds when every node is real, useful, and built to rank.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keyword-Rich Domains That Google Already Trusts
&lt;/h2&gt;

&lt;p&gt;The foundation is a portfolio of 1,500+ keyword-rich domains spanning .dev, .app, .world, .ai, and .com extensions. These aren't vanity URLs. These aren't placeholder sites waiting for content. Each domain was chosen because it communicates intent instantly and carries inherent trust signals.&lt;/p&gt;

&lt;p&gt;Google treats .dev and .app as secure-by-default TLDs because they enforce HTTPS at the registry level. Users click them more because the domain itself answers the question "what does this do?" When someone sees &lt;code&gt;carvaluation.app&lt;/code&gt;, they know exactly what to expect. When they see &lt;code&gt;genericstartup.com/tools/cars&lt;/code&gt;, they have to parse the path structure and guess.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjb5vj1ikogplyx83gx4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjb5vj1ikogplyx83gx4.jpg" alt="Dashboard interface showing gold trading analytics with colorful price chart, performance metrics, and trending data in dark mode" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The marketplace shows this strategy in action. Look at the names: &lt;code&gt;koreanfood.app&lt;/code&gt;, &lt;code&gt;[goldtrade.app](https://goldtrade.app/)&lt;/code&gt;, &lt;code&gt;carvaluation.app&lt;/code&gt;, &lt;code&gt;pokezonline.app&lt;/code&gt;, and dozens more. Each name is a micro-product waiting to launch. When you own the exact-match domain for a tool category, you start with positioning advantage before writing a single line of code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1,500+ keyword-rich domains across .dev, .app, .world, .link, and .com create instant topical relevance and trust signals that generic domains take months to build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why new domains in the mesh begin receiving impressions within days, not months. The domain name itself is a relevance signal. No amount of on-page SEO on a generic domain can replicate the clarity of a purpose-built keyword domain. The difference between "try our car valuation tool at genericstartup.com/tools/cars" and "visit carvaluation.app" is the difference between explaining and being understood immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fystu7bi53uq7uye1nxyj.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fystu7bi53uq7uye1nxyj.jpeg" alt="Microsoft Bing Webmaster Tools dashboard showing grounding queries table with search terms, intent types, topics, citation counts and share percentages" width="799" height="398"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why TLD Choice Actually Matters
&lt;/h3&gt;

&lt;p&gt;The choice of TLD matters more than most founders realize. We covered the strategic differences in depth when comparing &lt;a href="https://visionvix.com/app-vs-com/" rel="noopener noreferrer"&gt;.app versus .com domains&lt;/a&gt;, but the short version is this: modern TLDs carry semantic weight.&lt;/p&gt;

&lt;p&gt;A .dev domain signals developer tools. A .app domain signals an application. A .world domain signals a platform or ecosystem. Google doesn't just see these as interchangeable alternatives to .com. It treats them as categorization hints.&lt;/p&gt;

&lt;p&gt;For a deeper explanation of how this works at the registry level, understanding &lt;a href="https://visionvix.com/what-is-a-top-level-domain/" rel="noopener noreferrer"&gt;what a top-level domain actually is&lt;/a&gt; helps clarify why TLD choice affects both ranking speed and user perception.&lt;/p&gt;

&lt;p&gt;Below you can see our Google Analytics data: in just about two weeks, roughly 580 of our .app domains went live, and the traffic skyrocketed almost immediately. The pattern is clear. These are keyword-rich domains that Google understands and rewards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdinhuzg2yhxz01i21jmc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdinhuzg2yhxz01i21jmc.jpg" alt="Analytics dashboard showing 2.8k active users, usage graphs over time, and geographic distribution with United States, Ireland, and Pakistan listed as top countries" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The engagement metrics prove this pattern. Analytics show 2.8k active users and 9.4k events tracked across the mesh. Traffic originates from the US, Hong Kong, Singapore, Germany, and the UK. Domains act as connectors, not isolated endpoints.&lt;/p&gt;

&lt;p&gt;Some domains gain impressions within two weeks of launch. Some domains rank with zero backlinks because the on-page relevance and user engagement signals are strong enough to trigger indexing and ranking without external validation.&lt;/p&gt;
&lt;h1&gt;
  
  
  Our Domains: Purpose-Built, Keyword-Rich Assets for Digital Products
&lt;/h1&gt;

&lt;p&gt;At VisionVix, we don’t collect domains—we build digital opportunities around them.&lt;/p&gt;

&lt;p&gt;Every domain in our portfolio is selected with a clear objective: to become the foundation for a practical micro-tool, AI agent, software application, or SEO-driven growth asset. We focus on domains that combine strong keywords, user intent, and real commercial potential.&lt;/p&gt;

&lt;p&gt;Our expertise lies in identifying premium &lt;strong&gt;.app&lt;/strong&gt; and &lt;strong&gt;.dev&lt;/strong&gt; domains that are clear, memorable, and aligned with future product opportunities. These domains are not simply names sitting unused—they are strategic digital assets designed for development, deployment, and long-term value creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhs4mp47m66h140qcy9r3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhs4mp47m66h140qcy9r3.jpg" alt="visionvix main landing page" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Keyword-Rich Domains Create Value
&lt;/h3&gt;

&lt;p&gt;A strong keyword domain gives a product an immediate advantage by connecting directly with what users are searching for. Keyword-rich domains can help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve search visibility and relevance&lt;/li&gt;
&lt;li&gt;Communicate product purpose instantly&lt;/li&gt;
&lt;li&gt;Build credibility with users&lt;/li&gt;
&lt;li&gt;Increase organic click potential&lt;/li&gt;
&lt;li&gt;Support efficient customer acquisition&lt;/li&gt;
&lt;li&gt;Strengthen SEO ecosystems and content strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When combined with a useful product and strong execution, a focused exact-match domain provides a powerful foundation for building online authority.&lt;/p&gt;
&lt;h3&gt;
  
  
  Our Domain Acquisition Strategy
&lt;/h3&gt;

&lt;p&gt;We select domains based on a combination of market data, usability, and future development potential. Each domain is reviewed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relevant search keywords&lt;/li&gt;
&lt;li&gt;Commercial intent&lt;/li&gt;
&lt;li&gt;Product alignment&lt;/li&gt;
&lt;li&gt;Brand potential&lt;/li&gt;
&lt;li&gt;Trademark availability&lt;/li&gt;
&lt;li&gt;Historical quality, including Wayback and backlink checks&lt;/li&gt;
&lt;li&gt;Integration potential within our 500+ tool development ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our evaluation process considers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search volume&lt;/li&gt;
&lt;li&gt;Keyword competitiveness&lt;/li&gt;
&lt;li&gt;Traffic opportunity&lt;/li&gt;
&lt;li&gt;CPC value&lt;/li&gt;
&lt;li&gt;SERP landscape&lt;/li&gt;
&lt;li&gt;Brandability&lt;/li&gt;
&lt;li&gt;Compliance requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach ensures our portfolio consists of practical, build-ready assets rather than speculative domain holdings.&lt;/p&gt;
&lt;h3&gt;
  
  
  Examples of High-Intent .app Domains
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjzcsc9vw00bbpbd5w5c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjzcsc9vw00bbpbd5w5c.jpg" alt="Examples of High-Intent .app Domains" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selected examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;koreanfood.app&lt;/li&gt;
&lt;li&gt;goldtrade.app&lt;/li&gt;
&lt;li&gt;ailoan.app&lt;/li&gt;
&lt;li&gt;aieuct.app&lt;/li&gt;
&lt;li&gt;carvaluation.app&lt;/li&gt;
&lt;li&gt;pokezonline.app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each domain is short, memorable, and instantly communicates its intended use case—making it suitable for AI tools, applications, marketplaces, and specialised digital services.&lt;/p&gt;

&lt;p&gt;At VisionVix, we believe the right domain is more than an address—it is the starting point for building valuable products and scalable digital experiences.&lt;/p&gt;
&lt;h2&gt;
  
  
  Every Domain in the SEO Network Mesh Meets Product-Grade Compliance
&lt;/h2&gt;

&lt;p&gt;Unlike a PBN where sites exist only to pass link juice, every domain in the mesh is a real product with real infrastructure. Each one ships with fully built landing pages, working AI tools or calculators, SSL certificates, strict security headers, privacy policies, terms of service, cookie consent, structured data, semantic HTML, agentic browsing readiness, high PageSpeed scores, and clean Next.js or static builds.&lt;/p&gt;

&lt;p&gt;The audit results prove this. WebsiteAudit.dev scores 95/100. SecurityAudit.dev scores 100/100. VisionVix.world receives an A-grade security header report.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmtnyx62ilhchbmm0c21h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmtnyx62ilhchbmm0c21h.jpeg" alt=" Security scan results showing Grade A for visionvix.world with green checkmarks for Content-Security-Policy and other security headers" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agentic browsing audits pass with perfect scores. &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; consistently shows 100/100 performance, SEO, and best practices across the portfolio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9wvuv4ekluzv6bbh4yb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9wvuv4ekluzv6bbh4yb.jpeg" alt="Performance audit dashboard showing perfect 100 scores for Performance, Accessibility, Best Practices, SEO, and 2/2 Agentic Browsing" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Production-Grade Infrastructure on Every Domain
&lt;/h3&gt;

&lt;p&gt;These aren't placeholder sites thrown up to game search engines. They're production-grade applications built to survive compliance audits from enterprise customers, legal review, and accessibility standards. Every domain includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSL certificates&lt;/strong&gt; issued and auto-renewed through Let's Encrypt or Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security headers&lt;/strong&gt; enforcing Content Security Policy, Strict Transport Security, and X-Frame-Options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy and legal pages&lt;/strong&gt; written by actual legal review, not copied templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured data markup&lt;/strong&gt; using JSON-LD for breadcrumbs, organization, and product schemas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic HTML&lt;/strong&gt; with proper heading hierarchy, ARIA labels, and landmark roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile responsiveness&lt;/strong&gt; tested across viewport sizes and touch interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cookie consent flows&lt;/strong&gt; that comply with GDPR and CCPA requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility readiness&lt;/strong&gt; including keyboard navigation, screen reader support, and contrast ratios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of quality creates real defensibility. When an enterprise evaluates a tool, they run security scans. When a journalist references a calculator, they check the about page. When an AI agent crawls a domain, it expects structured data.&lt;/p&gt;

&lt;p&gt;The mesh survives these checks because every site was built to pass them. The infrastructure isn't optimized for SEO tricks. It's optimized for real use.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Compliance Matters for Rankings
&lt;/h3&gt;

&lt;p&gt;Google's algorithm doesn't just measure backlinks and keywords. It measures user experience signals. Sites that load fast, work on mobile, respect privacy, and follow web standards send trust signals. Sites that break on mobile, load slowly, or lack legal pages send warning signals.&lt;/p&gt;

&lt;p&gt;When you build 1,500 sites, you can't manually tune each one. You need infrastructure that makes compliance automatic. That's why every domain in the mesh inherits security headers, structured data, and legal pages from the same base template. One update cascades across the network.&lt;/p&gt;

&lt;p&gt;This approach also protects against manual review. When Google's quality raters spot-check sites in the mesh, they find real tools with real users and real infrastructure. There's nothing to penalize.&lt;/p&gt;

&lt;p&gt;For a detailed walkthrough of &lt;a href="https://accessibilityaudit.dev/blog/find-fix-website-accessibility-issues" rel="noopener noreferrer"&gt;finding and fixing website accessibility issues&lt;/a&gt;, we covered the full process in an earlier post. The same principles apply across the mesh. Accessibility isn't a nice-to-have. It's a ranking signal.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Keyword-Rich Domain Strategy Behind the Largest AI Agent Marketplace
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F12d2voeox4p36xywxv8i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F12d2voeox4p36xywxv8i.jpg" alt="VisionVix Marketplace interface showing split-screen city crosswalk images with Vision Analysis feature and statistics displaying 1000+ domains and 500+ AI tools" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The marketplace approach amplifies the mesh strategy. Instead of building one massive domain with hundreds of subdirectories, we built hundreds of standalone domains, each hosting a single tool or category. Then we connected them through &lt;a href="https://visionvix.app/" rel="noopener noreferrer"&gt;the VEX Domain Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each domain in the marketplace is a live product. Users can visit &lt;code&gt;goldtrade.app&lt;/code&gt; and get real-time precious metal price data. They can visit &lt;code&gt;carvaluation.app&lt;/code&gt; and estimate vehicle worth. They can visit &lt;code&gt;koreanfood.app&lt;/code&gt; and explore recipes with nutritional breakdowns.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over 1 million AI citations to VisionVix.com in seven months proves the compounding effect of a distributed product mesh built on keyword-rich domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The marketplace itself becomes a hub. It lists every domain, categorizes them by function, and surfaces them to users searching for specific tools. This creates a natural discovery loop. A user finds one tool, sees the marketplace, discovers three more related tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb72m98y4jiazb3ejkfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb72m98y4jiazb3ejkfr.png" alt="visionvix ai agent marketplace user dashboard and tools" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Standalone Domains Beat Subdirectories
&lt;/h3&gt;

&lt;p&gt;There's a persistent debate in SEO about whether subdomains or subdirectories rank better. We skipped the debate entirely and went straight to standalone domains. Here's why.&lt;/p&gt;

&lt;p&gt;Each domain is an independent entity. It can rank for its own keywords without competing with sibling pages. It can earn its own backlinks. It can build its own brand. It can be sold, spun off, or repurposed without affecting the rest of the mesh.&lt;/p&gt;

&lt;p&gt;Subdirectories force everything under one domain authority. If the parent domain gets penalized, every subdirectory suffers. If one subdirectory attracts spam, the entire domain risks contamination. Standalone domains isolate risk.&lt;/p&gt;

&lt;p&gt;More importantly, standalone domains create more SERP real estate. When someone searches for "car valuation calculator," we can occupy multiple positions with &lt;code&gt;carvaluation.app&lt;/code&gt;, &lt;code&gt;carcalculator.dev&lt;/code&gt;, and &lt;code&gt;vehiclevalue.world&lt;/code&gt;. Subdirectories under one domain would only let us occupy one position.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F99340d8xbwj8nyc8xmf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F99340d8xbwj8nyc8xmf2.png" alt="Person at desk wearing headphones working on computer displaying grid of colorful mini tool icons including calculator, converter, and generator" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The mesh isn't just domains. It's functionality. The ecosystem includes mini tools and &lt;a href="https://apps.visionvix.com/" rel="noopener noreferrer"&gt;160+ free calculators&lt;/a&gt; (but we're adding daily) distributed across the portfolio. These aren't demo apps or MVPs. They're production tools that thousands of people use daily to solve real problems.&lt;/p&gt;

&lt;p&gt;The marketplace model also scales horizontally. We can launch 50 new domains in a weekend without touching existing infrastructure. Each new domain adds another node to the mesh. Each node compounds the authority of the network.&lt;/p&gt;
&lt;h3&gt;
  
  
  How We Choose Which Domains to Build
&lt;/h3&gt;

&lt;p&gt;Not every keyword-rich domain is worth building. The decision framework is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does the domain name instantly communicate what the tool does?&lt;/strong&gt; If someone sees &lt;code&gt;goldtrade.app&lt;/code&gt;, do they immediately understand it's about precious metal trading? If yes, continue. If no, skip.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is there enough search volume to justify the build?&lt;/strong&gt; We don't need millions of searches per month. We need enough to generate a few hundred visits per month once ranked. Anything above 1,000 monthly searches is worth building.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can we build a real tool, not just a landing page?&lt;/strong&gt; If the domain is &lt;code&gt;calculator.app&lt;/code&gt;, we need to build an actual calculator. If we can't deliver the function the domain promises, we don't launch it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does it fit into a category cluster?&lt;/strong&gt; Isolated domains are harder to rank. Domains that connect to a category cluster inherit authority faster. If we already have five finance-related tools, adding &lt;code&gt;goldtrade.app&lt;/code&gt; strengthens the entire finance cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is the domain short, memorable, and easy to type?&lt;/strong&gt; Long domains don't convert. Domains with hyphens look spammy. Domains that require explanation lose users. We filter ruthlessly for clarity.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmlp1ey2dup76mhjkoy8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmlp1ey2dup76mhjkoy8.jpg" alt="Analytics dashboard showing 5k event count, 706 active users, 692 new users, with a line graph tracking metrics over 30 days" width="712" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using these filters, we've built a portfolio of 1,500+ domains. Each one passes the test. Each one adds value to the mesh.&lt;/p&gt;
&lt;h2&gt;
  
  
  Natural Cross-Linking Creates Sideways Authority Flow
&lt;/h2&gt;

&lt;p&gt;Traditional SEO focuses on hub-and-spoke models. You build one authoritative hub, then create spokes that link back to it. The hub gets stronger, the spokes stay weak.&lt;/p&gt;

&lt;p&gt;The mesh model works differently. Every domain links to related domains. Authority flows sideways, not just upward. When &lt;code&gt;carvaluation.app&lt;/code&gt; links to &lt;code&gt;carcalculator.dev&lt;/code&gt; and &lt;code&gt;vehiclevalue.world&lt;/code&gt;, all three benefit. When &lt;code&gt;goldtrade.app&lt;/code&gt; links to &lt;code&gt;silverprices.world&lt;/code&gt; and &lt;code&gt;platinumtrends.dev&lt;/code&gt;, the entire precious metals cluster gains strength.&lt;/p&gt;

&lt;p&gt;This creates a compounding effect. Each new domain doesn't just add one node. It adds connections between existing nodes. The network becomes denser, not just larger.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some domains gain traction and rank within two weeks of launch with zero backlinks due to strong on-page signals and user engagement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google doesn't penalize this because the links are contextually relevant and user-focused. A user on &lt;code&gt;carvaluation.app&lt;/code&gt; who needs a more detailed breakdown genuinely benefits from a link to &lt;code&gt;carcalculator.dev&lt;/code&gt;. The link serves the user, not just the algorithm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94fzxpkpdjjkr54vord5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F94fzxpkpdjjkr54vord5.jpeg" alt="Google Search Console Performance dashboard showing 0 total clicks, 2.82k impressions, 0% CTR, and 67.8 average position over 3 months with a line graph displaying click trends from May to July 2026" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Difference Between a Mesh and a PBN
&lt;/h3&gt;

&lt;p&gt;Private blog networks (PBNs) are built to manipulate rankings. Sites exist only to pass link juice. Content is thin. User engagement is nonexistent. Google actively hunts for PBNs and penalizes them.&lt;/p&gt;

&lt;p&gt;The mesh is the opposite. Every site is a real product with real users. Links exist because they help users navigate between related tools. Content is dense, functional, and valuable. Engagement metrics are strong because people actually use the tools.&lt;/p&gt;

&lt;p&gt;The difference shows up in the data. PBNs see zero engagement, high bounce rates, and no returning visitors. The mesh sees 2.8k active users, 9.4k tracked events, and traffic from five continents. Users don't land on a mesh site and immediately bounce. They use the tool, explore related tools, and return later.&lt;/p&gt;

&lt;p&gt;This is why the mesh survives algorithm updates. Google's core updates target manipulation. They don't target real products with real users.&lt;/p&gt;
&lt;h3&gt;
  
  
  How We Structure Cross-Links
&lt;/h3&gt;

&lt;p&gt;Cross-linking isn't random. We follow a structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Category clusters:&lt;/strong&gt; All finance tools link to each other. All calculator tools link to each other. All AI tools link to each other. This creates topical authority within each cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Related tools:&lt;/strong&gt; A car valuation tool links to a car calculator, a car insurance estimator, and a vehicle history checker. These are natural next steps for the same user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hub pages:&lt;/strong&gt; The marketplace acts as a central directory. Every tool links back to the marketplace. The marketplace links to every tool. This creates a strong hub without forcing all authority through a single domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User intent flows:&lt;/strong&gt; We map user journeys and link accordingly. Someone using a gold price tracker might next want a silver price tracker, a precious metals calculator, or a market trend analyzer. We link those tools together.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffujdnbj09u1naex1ypa7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffujdnbj09u1naex1ypa7.jpeg" alt="Google Search Console Performance dashboard showing 0 total clicks, 435 impressions, 0% CTR, and 76.6 average position over 3 months with a line graph displaying click trends" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is a network where authority flows naturally. No forced linking. No manipulation. Just a well-structured ecosystem where every link makes sense.&lt;/p&gt;
&lt;h2&gt;
  
  
  Engagement Data Confirms the Mesh Is Working
&lt;/h2&gt;

&lt;p&gt;Traffic numbers tell part of the story. Engagement metrics tell the rest.&lt;/p&gt;

&lt;p&gt;Across the mesh, we track 2.8k active users and 9.4k events. Traffic comes from the US, Hong Kong, Singapore, Germany, and the UK. Users aren't just landing on a page and leaving. They're clicking through tools, exploring calculators, and interacting with the products.&lt;/p&gt;

&lt;p&gt;The Bing Webmaster Tools dashboard shows the compounding effect. Over 1 million AI citations to VisionVix.com in seven months. That's not a paid promotion number. That's organic discovery driven by the mesh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmohiz7dmcci5r06opimz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmohiz7dmcci5r06opimz.jpeg" alt="Google Search Console Performance dashboard showing 2 total clicks, 1.45k impressions, 0.1% CTR, and 93.3 average position over 3 months" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Early Impressions on New Domains
&lt;/h3&gt;

&lt;p&gt;Some domains start generating impressions within two weeks of launch. No backlinks. No outreach. Just strong on-page signals and keyword-rich domain names.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;brandmonitoring.dev:&lt;/strong&gt; 2.82k impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aiagentready.dev:&lt;/strong&gt; 1.45k impressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aieuact.dev:&lt;/strong&gt; 435 impressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These numbers aren't massive, but they prove the model works. A new domain with zero external authority can gain visibility purely through topical relevance, user engagement, and semantic clarity.&lt;/p&gt;

&lt;p&gt;You can't manually configure 1,500 domains. You can't hand‑write privacy policies for 800 products. You can't individually optimize PageSpeed scores for hundreds of tools. The system works because the infrastructure is templated, automated, and tested. &lt;a href="https://github.com/VisionVix-HQ/visionvix-domains" rel="noopener noreferrer"&gt;Our GitHub repository&lt;/a&gt; shows exactly how this happens: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16mwf4584sq1x5opg86v.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16mwf4584sq1x5opg86v.jpeg" alt="GitHub repository page for visionvix-domains showing a file list with deployment commits for batch 1.7 and 26.6 domains" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When an AI agent like ChatGPT or Perplexity needs a calculator, it looks for structured data, semantic HTML, and clear function definitions. The mesh provides all three. That's why VisionVix.com receives over 1 million citations. The infrastructure is built for agentic discovery, not just human search.&lt;/p&gt;
&lt;h3&gt;
  
  
  PageSpeed and Core Web Vitals Across the Mesh
&lt;/h3&gt;

&lt;p&gt;Google's ranking algorithm prioritizes user experience. That means fast load times, mobile responsiveness, and Core Web Vitals scores. Every domain in the mesh is built to pass these checks.&lt;/p&gt;

&lt;p&gt;PageSpeed Insights consistently shows 100/100 performance, SEO, and best practices. This isn't an accident. The base template uses static generation where possible, lazy-loads images, inlines critical CSS, and preloads fonts. Every performance optimization is baked into the deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futvkqful7mjuclpregu5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Futvkqful7mjuclpregu5.jpeg" alt="Dashboard showing AI Agent readiness score of 89 with metrics for discovery fees, AI bot access, and semantic HTML progress bars" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://aiagentready.dev/" rel="noopener noreferrer"&gt;AI agent readiness&lt;/a&gt; score of 89/100 across the mesh confirms that these sites aren't just ranking for traditional search. They're optimized for the next wave of search: AI agents parsing structured data, answering user queries, and citing authoritative sources.&lt;/p&gt;

&lt;p&gt;When you launch 1,500 sites, you can't manually optimize each one. The infrastructure has to make performance automatic. That's why we use Next.js with static export, host on Cloudflare's edge network, and compress all assets at build time.&lt;/p&gt;

&lt;p&gt;The result: every site loads in under two seconds on mobile, scores 100/100 on PageSpeed, and passes Core Web Vitals. These signals compound with keyword relevance and user engagement to create fast-ranking domains.&lt;/p&gt;
&lt;h3&gt;
  
  
  Security Audits and Compliance Scores
&lt;/h3&gt;

&lt;p&gt;Enterprise customers and AI agents both care about security. A site with weak security headers, missing SSL, or unpatched vulnerabilities gets flagged. A site with A-grade security, strict CSP, and HSTS enforcement gets trusted.&lt;/p&gt;

&lt;p&gt;SecurityAudit.dev scores 100/100. VisionVix.world receives an A-grade security header report. Every domain in the mesh enforces HTTPS, sets secure cookies, blocks clickjacking, and prevents XSS attacks.&lt;/p&gt;

&lt;p&gt;We didn't build this level of security because it's a nice-to-have. We built it because it's a ranking signal. Google's algorithm doesn't explicitly say "sites with CSP headers rank higher," but the correlation is clear. Sites that follow web standards, protect user data, and enforce security best practices consistently rank better than sites that don't.&lt;/p&gt;

&lt;p&gt;For a detailed walkthrough of &lt;a href="https://securityaudit.dev/blog/website-security-scanning-small-business-owners" rel="noopener noreferrer"&gt;website security scanning for small business owners&lt;/a&gt;, we covered the full methodology in an earlier post. The same security infrastructure powers every domain in the mesh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgolieiter18fbusk5bh7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgolieiter18fbusk5bh7.jpeg" alt="Dashboard showing Agentic Browsing audit results with 3/3 score, displaying passed audits and not applicable WebMCP checks" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Scaling the Mesh: Deployment Pipeline and Automation
&lt;/h2&gt;

&lt;p&gt;Building 1,500 domains manually would take years. We built them in seven months because the entire deployment pipeline is automated.&lt;/p&gt;

&lt;p&gt;Every new domain starts from a base template. The template includes the full Next.js project structure, security headers, legal pages, structured data, and semantic HTML. When we decide to launch a new domain, we don't start from scratch. We copy the template, swap the branding, deploy the unique tool logic, and push to production.&lt;/p&gt;

&lt;p&gt;The deployment script handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain registration through Cloudflare or Namecheap&lt;/li&gt;
&lt;li&gt;DNS configuration pointing to Cloudflare's edge network&lt;/li&gt;
&lt;li&gt;SSL certificate provisioning through Let's Encrypt&lt;/li&gt;
&lt;li&gt;GitHub repository creation and initial commit&lt;/li&gt;
&lt;li&gt;Vercel or Netlify deployment with environment variables&lt;/li&gt;
&lt;li&gt;Sitemap generation and submission to Google Search Console&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On macOS or Linux, the process looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./deploy-new-domain.sh carvaluation.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, PowerShell handles the same flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$DomainName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"carvaluation.app"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\deploy-new-domain.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$DomainName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script runs for about three minutes. At the end, the domain is live, SSL is active, and the site is submitted to search engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Once, Reuse Forever
&lt;/h3&gt;

&lt;p&gt;The base template is the secret to scaling. Instead of rebuilding common infrastructure for every domain, we built it once and reused it 1,500 times.&lt;/p&gt;

&lt;p&gt;The template includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Legal pages:&lt;/strong&gt; Privacy policy, terms of service, cookie policy, GDPR compliance notices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security headers:&lt;/strong&gt; CSP, HSTS, X-Frame-Options, X-Content-Type-Options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured data:&lt;/strong&gt; JSON-LD schemas for organization, breadcrumbs, and products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic HTML:&lt;/strong&gt; Proper heading hierarchy, ARIA labels, landmark roles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata:&lt;/strong&gt; Open Graph tags, Twitter cards, canonical URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics integration:&lt;/strong&gt; Google Analytics, Bing Webmaster Tools, Cloudflare Analytics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cookie consent:&lt;/strong&gt; GDPR-compliant consent banners with opt-in/opt-out flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we update the template, we can cascade the update across the mesh. One commit updates 1,500 domains. This is how we stay compliant as privacy laws evolve and web standards change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgf6vbf1wyfjy4ryhoapx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgf6vbf1wyfjy4ryhoapx.jpeg" alt="Website audit dashboard showing a 95/100 score with green metrics for analytics, consent, SEO, security, and accessibility checks&lt;br&gt;
" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://websiteaudit.dev/" rel="noopener noreferrer"&gt;WebsiteAudit.dev&lt;/a&gt; is the compliance gate for the entire mesh. It proves every domain is a real product. The audits cover analytics, consent, SEO, social tags, schema, security, performance, and accessibility. &lt;/p&gt;

&lt;h3&gt;
  
  
  AI Agents in the Deployment Loop
&lt;/h3&gt;

&lt;p&gt;We don't manually configure every domain. AI agents handle repetitive tasks like generating legal pages, writing metadata, and creating structured data.&lt;/p&gt;

&lt;p&gt;When we launch &lt;code&gt;goldtrade.app&lt;/code&gt;, an AI agent reads the domain name, infers the tool category, and generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A privacy policy tailored to precious metals price tracking&lt;/li&gt;
&lt;li&gt;SEO metadata describing the tool's function&lt;/li&gt;
&lt;li&gt;Structured data schemas for financial calculators&lt;/li&gt;
&lt;li&gt;A terms of service page covering data sources and disclaimers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't lazy automation. It's precise automation. The agent follows templates, enforces legal compliance, and ensures consistency across the mesh. A human reviews the output, but the agent handles 90% of the work.&lt;/p&gt;

&lt;p&gt;This is how we built 1,500 domains in seven months. We didn't hire a massive team. We built infrastructure that makes launching a new domain a three-minute task instead of a three-week project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Founders
&lt;/h2&gt;

&lt;p&gt;Most founders think about SEO as a backlink arms race. They assume you need thousands of high-authority links to rank. The mesh proves otherwise.&lt;/p&gt;

&lt;p&gt;You can rank with zero backlinks if your domain name is keyword-rich, your on-page signals are strong, and your user engagement is real. You can scale to 1,500 domains if your infrastructure is templated and your deployment is automated. You can generate over 1 million AI citations if every site is built for agentic discovery.&lt;/p&gt;

&lt;p&gt;The mesh model works because it's built on real products, not manipulation. Every domain solves a real problem. Every tool serves real users. Every link helps someone find the next step in their journey.&lt;/p&gt;

&lt;p&gt;If you're building in public, shipping fast, or scaling a portfolio of micro-products, this model is worth studying. The infrastructure compounds. The authority flows sideways. The rankings accelerate with every new node.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; We Built 1,500 Keyword-Rich Domains Before Chasing Backlinks — Here's What Happened&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How 1,500 Keyword Domains Generated 1M+ AI Citations in 7 Months Without Traditional SEO&lt;/li&gt;
&lt;li&gt;Building Authority Sideways: The Domain Mesh Strategy That Beat Traditional Link Building&lt;/li&gt;
&lt;li&gt;From Zero to 1M AI Citations: Why We Built Products on 1,500 Domains First, SEO Second&lt;/li&gt;
&lt;li&gt;Keyword-Rich Domains as Infrastructure: A Network-First Approach to Organic Authority&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;1500-keyword-domains-generated-1m-ai-citations-without-backlinks&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;seo, webdev, startup, domains&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>startup</category>
      <category>domains</category>
    </item>
    <item>
      <title>llms.txt vs mcp.json: Why Your Site Needs Both (And What They Actually Do)</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Mon, 20 Jul 2026 11:11:51 +0000</pubDate>
      <link>https://dev.to/julianneagu/llmstxt-vs-mcpjson-why-your-site-needs-both-and-what-they-actually-do-1kaf</link>
      <guid>https://dev.to/julianneagu/llmstxt-vs-mcpjson-why-your-site-needs-both-and-what-they-actually-do-1kaf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; llms.txt helps AI assistants &lt;em&gt;read&lt;/em&gt; your site by mapping your best content. mcp.json lets AI agents &lt;em&gt;call&lt;/em&gt; your site by exposing structured tools. They solve different problems, and your site needs both if you want to show up in agent-driven answers instead of getting skipped.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I updated our website generator last week to auto-deploy llms.txt files in every new site. Two days later, a founder asked me: "So what exactly does this do that my sitemap doesn't?" Fair question. I realized I'd been lumping llms.txt and mcp.json together as generic "AI stuff" when they're actually solving completely different problems.&lt;/p&gt;

&lt;p&gt;Here's what I should have said from the start: we're building websites for two audiences now. The first audience is human visitors who click through your navigation and read your pages. The second audience is AI agents that crawl your site, extract information, and either cite your content in chat responses or call your site's APIs to take actions on behalf of users.&lt;/p&gt;

&lt;p&gt;Those agents need different signals than humans do. Your homepage hero section might look great to a visitor, but an AI assistant reading your HTML sees twenty CSS classes, three analytics scripts, and a pile of nested divs before it finds your actual value proposition. That's the gap llms.txt and mcp.json are filling.&lt;/p&gt;

&lt;p&gt;I check this on &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; for every site we ship now. A perfect 3/3 Agentic Browsing audit score requires:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi04u4jeeawlimgvk2qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi04u4jeeawlimgvk2qx.png" alt="using  PageSpeed Insights form google to find if the site is ai agent ready" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They're not competing standards. They're complementary files that tell AI agents two fundamentally different things about your site. One is a map. The other is a menu of actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt: A Content Map for AI Reading
&lt;/h2&gt;

&lt;p&gt;Think of llms.txt as a table of contents written specifically for AI assistants. It's a Markdown file that lists your most important pages with brief descriptions, so when ChatGPT or Perplexity encounters your site, it knows exactly where the valuable content lives.&lt;/p&gt;

&lt;p&gt;Instead of scraping your homepage HTML and guessing which links matter, the AI reads your llms.txt and finds your pricing page, your feature comparison, your case studies, your best blog posts. No guessing. No hallucinated URLs from cached training data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;llms.txt gives AI assistants a clean, structured summary of your most important pages, so they cite canonical content instead of scraping cluttered HTML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built this into version 183.71 of our website generator. Every site that runs the sitemap agent now gets an auto-generated llms.txt that includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The homepage with your site's core value proposition&lt;/li&gt;
&lt;li&gt;Key product or feature pages with descriptions&lt;/li&gt;
&lt;li&gt;Your most important blog posts or content&lt;/li&gt;
&lt;li&gt;Contact and about pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it doesn't include: privacy policies, terms of service, legal disclaimers. Those aren't "important content" per the emerging standard. AI assistants don't need to cite your cookie policy when someone asks what your product does.&lt;/p&gt;

&lt;p&gt;The practical impact: when someone asks ChatGPT "What does [your product] do?" or "How does [your company] approach X?", the AI can pull from your llms.txt map instead of guessing which page to read. It's the difference between an assistant citing your actual pricing page versus hallucinating a price from a cached blog post written two years ago.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing llms.txt by Hand
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8h9hn6r67n76gow79a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8h9hn6r67n76gow79a2.png" alt="Example llms.txt file structure with URLs and descriptions mapping a website's key pages for AI assistants" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The format is dead simple. Here's a real example from one of our sites:&lt;/p&gt;

&lt;p&gt;``markdown&lt;/p&gt;

&lt;h2&gt;
  
  
  My Product Name
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://example" rel="noopener noreferrer"&gt;https://example&lt;/a&gt;. com/&lt;br&gt;
The homepage. We help developers ship faster by automating repetitive setup tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example" rel="noopener noreferrer"&gt;https://example&lt;/a&gt;. com/pricing&lt;br&gt;
Pricing page. Three tiers: Free, Pro ($29/mo), Team ($99/mo).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example" rel="noopener noreferrer"&gt;https://example&lt;/a&gt;. com/blog/how-we-built-this&lt;br&gt;
Case study: how we reduced deployment time from 4 hours to 12 minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example" rel="noopener noreferrer"&gt;https://example&lt;/a&gt;. com/docs&lt;br&gt;
Documentation hub. Getting started guides and API reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://example" rel="noopener noreferrer"&gt;https://example&lt;/a&gt;. com/contact&lt;br&gt;
Contact page. Email &lt;a href="mailto:support@example"&gt;support@example&lt;/a&gt;. com or use the form.&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;That's it. One URL per line, followed by a one-sentence description. No XML. No schema markup. Just plain Markdown that both humans and AI can read. Save it as &lt;code&gt;llms.txt&lt;/code&gt; at your site root.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retroactive llms.txt for Existing Sites
&lt;/h3&gt;

&lt;p&gt;If you're adding llms.txt to a site that's already live, you need to decide which pages deserve to be in the map. Here's the heuristic I use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with your sitemap.xml and filter for pages with real content (not legal pages, not redirects)&lt;/li&gt;
&lt;li&gt;Rank pages by how often you &lt;em&gt;want&lt;/em&gt; them cited when someone asks about your product&lt;/li&gt;
&lt;li&gt;Keep the top 10-15 pages, write a one-line description for each&lt;/li&gt;
&lt;li&gt;Deploy the file and add it to your robots.txt&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The robots.txt part matters. Add this line so AI crawlers know the file exists:&lt;/p&gt;

&lt;p&gt;``txt&lt;br&gt;
User-agent: *&lt;br&gt;
Allow: /llms.txt&lt;/p&gt;

&lt;p&gt;Sitemap: &lt;a href="https://yoursite.com/sitemap.xml" rel="noopener noreferrer"&gt;https://yoursite.com/sitemap.xml&lt;/a&gt;&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg8o6fd1o7hjwaylfnyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg8o6fd1o7hjwaylfnyb.png" alt="robots.txt configuration file showing how to declare llms.txt location for AI crawler discovery&lt;br&gt;
" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most AI agents don't actively look for llms.txt yet. But the ones that do (including some custom research agents and newer Perplexity iterations) respect it immediately. You're getting in early, which means your content gets cited while competitors' sites get skipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  mcp.json: An Action API for AI Interaction
&lt;/h2&gt;

&lt;p&gt;Now here's where mcp.json (also called WebMCP) does something entirely different. It's not about reading your content. It's about calling your site. It's a machine-readable manifest that defines specific tools an AI agent can invoke to get structured data back.&lt;/p&gt;

&lt;p&gt;Think of it as an API specification, but designed for AI agents instead of developers. A typical mcp.json file might define tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_pricing&lt;/code&gt; → returns your current pricing tiers with exact dollar amounts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_contact&lt;/code&gt; → returns your support email and contact methods&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_faq&lt;/code&gt; → returns your frequently asked questions with answers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_availability&lt;/code&gt; → returns whether a product or service slot is available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an AI agent reads your mcp.json, it sees these tools and can call them during a conversation. A user asks "How much does the pro plan cost?" and instead of reading your pricing page HTML, the agent calls your &lt;code&gt;get_pricing&lt;/code&gt; tool and gets back clean JSON with the exact answer. No scraping. No guessing. No stale cache.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;mcp.json defines callable tools that give AI agents exact, structured answers: your real prices, your real email, your real product availability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the key architectural difference: &lt;strong&gt;llms.txt is passive.&lt;/strong&gt; It's a map. The AI reads it, then reads your pages. &lt;strong&gt;mcp.json is active.&lt;/strong&gt; It's a set of endpoints. The AI calls it and gets immediate structured responses.&lt;/p&gt;

&lt;p&gt;As we explain in our &lt;a href="https://aiagentready.dev/blog/structured-data-tips-ai-ready-websites" rel="noopener noreferrer"&gt;structured data tips for AI-ready websites&lt;/a&gt;, these agent-facing files complement traditional SEO signals like sitemaps and JSON-LD rather than replacing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing a Basic mcp.json Manifest
&lt;/h3&gt;

&lt;p&gt;The WebMCP spec is still evolving, but here's a minimal working example that defines two tools:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "name": "My Product API",&lt;br&gt;
  "version": "1.0.0",&lt;br&gt;
  "description": "Tools for interacting with My Product",&lt;br&gt;
  "tools": [&lt;br&gt;
    {&lt;br&gt;
      "name": "get_pricing",&lt;br&gt;
      "description": "Returns current pricing tiers and features",&lt;br&gt;
      "inputSchema": {&lt;br&gt;
        "type": "object",&lt;br&gt;
        "properties": {}&lt;br&gt;
      }&lt;br&gt;
    },&lt;br&gt;
    {&lt;br&gt;
      "name": "get_contact",&lt;br&gt;
      "description": "Returns contact email and support options",&lt;br&gt;
      "inputSchema": {&lt;br&gt;
        "type": "object",&lt;br&gt;
        "properties": {}&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  ],&lt;br&gt;
  "endpoints": {&lt;br&gt;
    "get_pricing": "https://example.com/api/pricing",&lt;br&gt;
    "get_contact": "https://example.com/api/contact"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each tool has a name, a description, and an endpoint URL. When an AI agent calls &lt;code&gt;get_pricing&lt;/code&gt;, it hits your &lt;code&gt;/api/pricing&lt;/code&gt; endpoint and expects JSON back. You control what data gets returned. The agent gets structured facts instead of parsing HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgltclp4yiuj8m5248w2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgltclp4yiuj8m5248w2b.png" alt="Code editor showing example mcp.json file with defined tools like get_pricing and get_contact alongside their schemas" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying mcp.json to Three Locations
&lt;/h3&gt;

&lt;p&gt;Here's the annoying part: the WebMCP standard is new, and different AI agents expect the manifest in different places. To maximize compatibility, deploy your mcp.json file to &lt;strong&gt;three locations&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/.well-known/webmcp.json&lt;/code&gt; (the official spec location)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/mcp.json&lt;/code&gt; (root fallback for agents that skip .well-known)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/agent.json&lt;/code&gt; (some tools look for this name)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes, it's redundant. Yes, it's a pain. But until the standard solidifies, this triple deployment gives you the best chance of being discovered by agent frameworks that are experimenting with different conventions.&lt;/p&gt;

&lt;p&gt;On most hosting platforms, you can set up a single source file and three symlinks or redirects. Here's the setup for a static site:&lt;/p&gt;

&lt;p&gt;``bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Create the master file
&lt;/h1&gt;

&lt;p&gt;cat &amp;gt; .well-known/webmcp.json &amp;lt;&amp;lt; EOF&lt;br&gt;
{&lt;br&gt;
  "name": "My Product API",&lt;br&gt;
  "version": "1.0.0",&lt;br&gt;
  ...&lt;br&gt;
}&lt;br&gt;
EOF&lt;/p&gt;

&lt;h1&gt;
  
  
  Create fallback copies
&lt;/h1&gt;

&lt;p&gt;cp .well-known/webmcp.json mcp.json&lt;br&gt;
cp .well-known/webmcp.json agent.json&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;If you're on Windows and using PowerShell, the workflow is similar but with different commands:&lt;/p&gt;

&lt;p&gt;``powershell&lt;/p&gt;

&lt;h1&gt;
  
  
  Create the directory if needed
&lt;/h1&gt;

&lt;p&gt;New-Item -ItemType Directory -Force -Path .well-known&lt;/p&gt;

&lt;h1&gt;
  
  
  Create the master file (use your actual JSON content here)
&lt;/h1&gt;

&lt;p&gt;@"&lt;br&gt;
{&lt;br&gt;
  "name": "My Product API",&lt;br&gt;
  "version": "1.0.0",&lt;br&gt;
  ...&lt;br&gt;
}&lt;br&gt;
"@ | Out-File -FilePath .well-known\webmcp.json -Encoding utf8&lt;/p&gt;

&lt;h1&gt;
  
  
  Create fallback copies
&lt;/h1&gt;

&lt;p&gt;Copy-Item .well-known\webmcp.json mcp.json&lt;br&gt;
Copy-Item .well-known\webmcp.json agent.json&lt;br&gt;
``&lt;/p&gt;

&lt;p&gt;Deploy all three files. When the standard settles, you can drop two of them. Until then, redundancy wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Makes a Good WebMCP Tool
&lt;/h3&gt;

&lt;p&gt;Not every API endpoint should be a WebMCP tool. The best tools return &lt;strong&gt;facts that change&lt;/strong&gt; or &lt;strong&gt;actions that matter&lt;/strong&gt;. Here's what works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_pricing&lt;/code&gt; → returns current prices (changes when you update pricing)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_availability&lt;/code&gt; → returns real-time availability (changes as inventory moves)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_latest_post&lt;/code&gt; → returns your most recent blog post (changes when you publish)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search_docs&lt;/code&gt; → searches your documentation (user-specific results)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bad tools:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_about_page&lt;/code&gt; → just returns static HTML (llms.txt already maps this)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_logo&lt;/code&gt; → returns an image URL (not structured data)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subscribe_newsletter&lt;/code&gt; → writes data (WebMCP is read-only for now)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern: if an AI agent calling your tool gets back structured data that's either dynamic or user-specific, it's a good tool. If it's just returning static content that could be read from a page, llms.txt is the better fit.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://aiagentready.dev/" rel="noopener noreferrer"&gt;AI Agent Ready&lt;/a&gt; to solve exactly this gap. It generates both files automatically, deploys them to the right locations, and validates that your site passes the Agentic Browsing checks. If you're shipping multiple sites or managing a portfolio, automation is the only way to keep up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenujxixretqc09b3ts6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fenujxixretqc09b3ts6a.png" alt="using a tool software to find if my site has llm.txt  or mcp.json and if is ai agent ready" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Deploy Both (Even Though Adoption Is Still Low)
&lt;/h2&gt;

&lt;p&gt;Neither llms.txt nor mcp.json is widely adopted yet. Most AI crawlers don't fully honor either one. So why am I building both into every site we ship?&lt;/p&gt;

&lt;p&gt;Because &lt;strong&gt;the cost is near zero&lt;/strong&gt; and &lt;strong&gt;the positioning advantage is real.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's my current ranking of what actually moves the needle for AI visibility today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sitemap + robots.txt that allow AI bots&lt;/strong&gt; (most impactful right now, every major AI crawler respects this, non-negotiable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-LD structured data&lt;/strong&gt; (actively used by Google today, immediate SEO benefit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;llms.txt&lt;/strong&gt; (emerging standard with growing adoption for AI-search citation, low cost, future-facing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mcp.json / WebMCP&lt;/strong&gt; (most forward-looking, least adopted, but the agent-actions layer that could matter enormously as browsing agents mature)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two are table stakes. If your site doesn't have a sitemap or blocks AI bots in robots.txt, you're invisible. The second two are bets on where agent behavior is heading in the next 6-12 months.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google added a fifth category to PageSpeed Insights in May 2026 called Agentic Browsing. It doesn't give a score out of 100; instead it shows a ratio like 3/3.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;llms.txt present and properly formatted&lt;/li&gt;
&lt;li&gt;mcp.json present with at least one callable tool&lt;/li&gt;
&lt;li&gt;sitemap.xml with all key pages included&lt;/li&gt;
&lt;li&gt;JSON-LD structured data on major pages&lt;/li&gt;
&lt;li&gt;passing accessibility checks (agents rely on semantic HTML)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most sites still score 0/3 or 1/3. Getting to 3/3 takes an afternoon of work. That gap is your positioning advantage right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four-Layer Agent-Ready Stack
&lt;/h2&gt;

&lt;p&gt;Here's how I think about the full stack for agent-ready websites. You need all four layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Discoverable (Sitemap + Robots.txt)
&lt;/h3&gt;

&lt;p&gt;AI crawlers need to know your site exists and which pages to index. This is the foundation. Without a sitemap, you're invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Understandable (JSON-LD Structured Data)
&lt;/h3&gt;

&lt;p&gt;Your pages need machine-readable context. JSON-LD tells AI agents what kind of page they're looking at (article, product, organization) and extracts key fields (author, price, date). This is how Google's Knowledge Graph works, and it's how AI search engines will cite you.&lt;/p&gt;

&lt;p&gt;For implementation patterns, the &lt;a href="https://visionvix.com/json-prompt-for-ai-agents/" rel="noopener noreferrer"&gt;JSON prompt guide for AI agents&lt;/a&gt; walks through specific schema markup for different page types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzq7tzkdtf1f9r1u5hvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzq7tzkdtf1f9r1u5hvk.png" alt="priority chart ranking AI-ready website techniques from sitemap and robots.txt at top to mcp.json at bottom by current impact" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Readable (llms.txt)
&lt;/h3&gt;

&lt;p&gt;Once an AI agent lands on your site, llms.txt tells it which pages matter most. This is the content map layer. It answers: "If you can only read five pages, read these five."&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Interactive (mcp.json)
&lt;/h3&gt;

&lt;p&gt;Finally, mcp.json exposes callable tools that let AI agents get exact answers without scraping. This is the actions layer. It answers: "If you need real-time data or want to verify a fact, call these endpoints."&lt;/p&gt;

&lt;p&gt;Each layer builds on the previous one. You can't skip straight to mcp.json if your sitemap blocks AI bots. But once you have layers 1 and 2 in place, adding layers 3 and 4 is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens If You Skip This
&lt;/h2&gt;

&lt;p&gt;Here's the risk: your competitors deploy llms.txt and mcp.json, and their content starts getting cited in AI-driven search results while yours gets skipped. A user asks "What are the best tools for X?" and ChatGPT cites three competitors but not you, because their sites gave the AI a clean map and yours made it scrape HTML.&lt;/p&gt;

&lt;p&gt;Or worse: a user asks "How much does [your product] cost?" and the AI hallucinates a price from a cached blog post written two years ago, because you didn't expose a &lt;code&gt;get_pricing&lt;/code&gt; tool that returns your current pricing.&lt;/p&gt;

&lt;p&gt;Agent-ready sites will win citations. Sites that ignore this layer will become invisible to the next generation of search behavior. That's not speculation. It's already happening in vertical AI search engines that prioritize structured data over keyword optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start With llms.txt, Add mcp.json Later
&lt;/h2&gt;

&lt;p&gt;If you're resource-constrained, start with llms.txt. It's a single Markdown file. You can write it by hand in ten minutes. Deploy it to your site root, add it to robots.txt, and you're done.&lt;/p&gt;

&lt;p&gt;Then, when you have time to build API endpoints, add mcp.json. Start with one or two simple tools like &lt;code&gt;get_pricing&lt;/code&gt; or &lt;code&gt;get_contact&lt;/code&gt;. Deploy the manifest to all three locations. Test it by checking the Agentic Browsing score on PageSpeed Insights.&lt;/p&gt;

&lt;p&gt;That's the pragmatic path: llms.txt this week, mcp.json next month. Both files are small. Both are future-facing. Both give you positioning advantage while most sites are still ignoring this layer entirely.&lt;/p&gt;

&lt;p&gt;But even if you're doing this manually for a single site, the work is worth it. We're not optimizing for today's AI agents. We're optimizing for the ones that launch six months from now, when agent-driven search is the default and sites without llms.txt and mcp.json are invisible.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; llms.txt vs mcp.json: Why Your Site Needs Both (And What They Actually Do)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI Agents Need Two Things From Your Site: A Map and a Menu&lt;/li&gt;
&lt;li&gt;llms.txt and mcp.json Aren't Competing—They're Solving Different Problems&lt;/li&gt;
&lt;li&gt;How to Make Your Site Readable and Actionable for AI Assistants&lt;/li&gt;
&lt;li&gt;The Real Difference Between llms.txt and mcp.json (With Implementation Examples)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;llms-txt-vs-mcp-json-why-your-site-needs-both&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, ai, tutorial, llms&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>llms</category>
    </item>
    <item>
      <title>The Domain Extension Wars: Why .ai and .app Are Crushing .com</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Mon, 29 Jun 2026 11:57:37 +0000</pubDate>
      <link>https://dev.to/julianneagu/the-domain-extension-wars-why-ai-and-app-are-crushing-com-2f3j</link>
      <guid>https://dev.to/julianneagu/the-domain-extension-wars-why-ai-and-app-are-crushing-com-2f3j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Tech companies are ditching .com for specialized TLDs like .ai, .app, and .dev. These extensions offer better SEO performance, clearer brand positioning, and signal innovation to investors. Anguilla earned $32 million from .ai domains alone in 2023 — that's 20% of their government revenue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The domain extension wars are heating up, and .com is losing ground fast. When I see a startup pitch deck with a .ai domain, my immediate reaction is different than seeing the same company with a .com extension. One signals cutting-edge innovation; the other feels like they settled for what was available.&lt;/p&gt;

&lt;p&gt;This shift isn't just cosmetic. &lt;strong&gt;New TLDs like .app, .dev, and .ai are becoming strategic business assets&lt;/strong&gt; that communicate industry focus, improve search rankings, and attract investor attention in ways that generic .com domains simply can't match.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Economics Behind the TLD Revolution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc54sv6vjarsti1tn940e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc54sv6vjarsti1tn940e.jpg" alt="AI Economy Use Cases: Which TLD to Choose Based on Business Type" width="705" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The numbers tell the story. In 2023 alone, the tiny Caribbean island of Anguilla generated approximately &lt;strong&gt;$32 million in revenue from .ai domain registrations&lt;/strong&gt; — that's roughly 20% of their entire government budget. When a two-letter country code becomes a major revenue stream, you know something fundamental has shifted in how businesses think about domains.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;According to the IMF, .ai domain revenue accounted for roughly 20% of Anguilla's total government revenues in 2023.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This explosion in .ai adoption reflects a broader trend: &lt;strong&gt;companies are choosing domains that immediately communicate their business focus&lt;/strong&gt; rather than settling for generic .com extensions. The semantic clarity of these new TLDs creates instant brand alignment that traditional domains can't provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the TLD Landscape
&lt;/h2&gt;

&lt;p&gt;A Top-Level Domain (TLD) is the suffix that follows the final dot in a web address — the ".com" in "google.com" or the ".ai" in "openai.ai". While this might seem like a technical detail, TLDs have evolved into powerful branding and positioning tools.&lt;/p&gt;

&lt;p&gt;Traditional TLDs served their purpose in the early internet era, but they lack the contextual precision that modern businesses need. When every company from law firms to restaurants uses .com, &lt;strong&gt;the extension becomes meaningless as a differentiator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a deeper dive into how TLDs function and their technical implications, I covered the fundamentals in &lt;a href="https://visionvix.com/what-is-a-top-level-domain/" rel="noopener noreferrer"&gt;my comprehensive TLD guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How TLDs Shape User Perception
&lt;/h3&gt;

&lt;p&gt;Users make split-second judgments based on domain extensions. A .dev domain immediately signals that you're looking at a developer-focused platform. A .ai extension suggests artificial intelligence capabilities, making &lt;strong&gt;&lt;a href="https://ogchecker.dev/blog/og-tags-social-media-preview-optimization" rel="noopener noreferrer"&gt;clear brand and metadata signals&lt;/a&gt;&lt;/strong&gt; even more important. These associations happen before users even read your content.&lt;/p&gt;

&lt;p&gt;Consider these psychological triggers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.ai domains&lt;/strong&gt; create expectations of machine learning, automation, and cutting-edge technology&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.app domains&lt;/strong&gt; suggest mobile-first experiences and software applications
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.dev domains&lt;/strong&gt; signal developer tools, programming resources, and technical expertise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.com domains&lt;/strong&gt; feel generic and established but lack industry specificity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison Table
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6z2eehnj231dd8xc7la.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6z2eehnj231dd8xc7la.jpg" alt="Table comparing 5 TLDs (.com, .ai, .app, .dev, .io) across purpose, SEO impact, and availability columns on dark background" width="663" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The .com vs Modern TLD Breakdown
&lt;/h2&gt;

&lt;p&gt;The fundamental difference between .com and semantic TLDs comes down to &lt;strong&gt;signal clarity&lt;/strong&gt;. A .com domain forces users to guess what your business does. A .ai domain tells them immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search Engine Optimization Advantages
&lt;/h3&gt;

&lt;p&gt;Modern TLDs offer distinct SEO benefits that .com domains can't match. Search engines increasingly prioritize &lt;strong&gt;semantic matching between domain extensions and user intent&lt;/strong&gt;. When someone searches for "AI writing tools," a website with a .ai domain has a natural advantage over the same content on a .com domain.&lt;/p&gt;

&lt;p&gt;Key SEO factors favoring newer TLDs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyword alignment&lt;/strong&gt;: .ai domains naturally rank better for AI-related searches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click-through rates&lt;/strong&gt;: Users click on domains that match their search intent
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical standards&lt;/strong&gt;: Many new TLDs enforce HTTPS by default, boosting Google rankings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core Web Vitals&lt;/strong&gt;: Newer domain infrastructure often performs better on speed metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Investment and Market Appeal
&lt;/h3&gt;

&lt;p&gt;Venture capitalists and angel investors are paying attention to domain choices. &lt;strong&gt;A well-chosen TLD signals that founders understand digital positioning&lt;/strong&gt; and are thinking strategically about their brand presence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Investors increasingly view .ai, .app, and .dev domains as indicators of future success and alignment with digital transformation trends.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't superficial preference — it reflects deeper business acumen. Companies that choose strategic TLDs often demonstrate the same forward-thinking approach in product development and market positioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Businesses Are Abandoning .com
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffya7zdwj8zt8zqvf5b6g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffya7zdwj8zt8zqvf5b6g.jpg" alt="Infographic showing four reasons businesses move beyond .com domains with icons for niche focus, SEO ranking, investor appeal, and domain availability" width="705" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The migration away from .com stems from three core limitations: &lt;strong&gt;positioning weakness, SEO disadvantages, and availability constraints&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Positioning and Brand Clarity
&lt;/h3&gt;

&lt;p&gt;When a business uses .app, .dev, or .ai, they immediately communicate their industry focus. This semantic precision helps companies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stand out in crowded markets&lt;/li&gt;
&lt;li&gt;Build instant credibility with target audiences
&lt;/li&gt;
&lt;li&gt;Align their digital identity with their core services&lt;/li&gt;
&lt;li&gt;Create memorable brand associations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare the positioning power: "mycompany.com" versus "mycompany.ai" for an artificial intelligence startup. The .ai extension does heavy lifting that .com simply cannot provide.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Performance Gap
&lt;/h3&gt;

&lt;p&gt;Search engines have evolved to understand semantic relationships between domain extensions and content. &lt;strong&gt;A .ai website discussing machine learning has inherent relevance advantages&lt;/strong&gt; over identical content on a .com domain.&lt;/p&gt;

&lt;p&gt;This semantic matching translates into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher click-through rates from search results&lt;/li&gt;
&lt;li&gt;Better keyword rankings for industry-specific terms&lt;/li&gt;
&lt;li&gt;Improved user engagement metrics&lt;/li&gt;
&lt;li&gt;Enhanced local and mobile search performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7plxipai61ehvbb11lxf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7plxipai61ehvbb11lxf.jpg" alt="Table comparing TLDs &amp;amp; SEO vs TLDs &amp;amp; Sitemaps, showing direct factors, how they help, key mechanisms, and takeaways" width="691" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Availability Crisis
&lt;/h3&gt;

&lt;p&gt;The .com namespace is effectively saturated. Finding a short, memorable .com domain that matches your business name requires either significant compromise or substantial financial investment, making &lt;strong&gt;&lt;a href="https://linkbuilder.dev/blog/evaluate-link-worthy-content-outreach" rel="noopener noreferrer"&gt;strategic online positioning&lt;/a&gt;&lt;/strong&gt; more important for new brands. Premium .com domains regularly sell for six or seven-figure amounts.&lt;/p&gt;

&lt;p&gt;Meanwhile, newer TLDs offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Greater name availability for exact business matches&lt;/li&gt;
&lt;li&gt;Shorter, more memorable domain options&lt;/li&gt;
&lt;li&gt;Keyword-rich domains that support SEO strategies&lt;/li&gt;
&lt;li&gt;Lower acquisition costs for premium names&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Strategic TLD Selection by Business Type
&lt;/h2&gt;

&lt;p&gt;Different business models benefit from different TLD strategies. The key is &lt;strong&gt;matching your domain extension to your primary value proposition&lt;/strong&gt; and target audience expectations.&lt;/p&gt;

&lt;p&gt;For a detailed comparison of .app versus .dev domains specifically, check out &lt;a href="https://visionvix.com/app-vs-dev/" rel="noopener noreferrer"&gt;my analysis of these two popular extensions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44zuwdf1koj2uz9zeam9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44zuwdf1koj2uz9zeam9.jpg" alt="Website homepage featuring dark blue interface with navigation menu, main heading about premium app domains for AI innovation, three service cards below with icons" width="799" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AI and Machine Learning Companies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Choose .ai&lt;/strong&gt; for businesses focused on artificial intelligence, machine learning, automation, or data science. This extension immediately communicates technical sophistication and innovation.&lt;/p&gt;

&lt;p&gt;Examples of effective .ai positioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS platforms with AI-powered features&lt;/li&gt;
&lt;li&gt;Machine learning consulting firms
&lt;/li&gt;
&lt;li&gt;Automated workflow tools&lt;/li&gt;
&lt;li&gt;Predictive analytics platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Software Applications and Mobile Apps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Choose .app&lt;/strong&gt; for companies building software applications, mobile apps, or digital tools. This extension suggests user-friendly interfaces and practical software solutions.&lt;/p&gt;

&lt;p&gt;Ideal .app use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile app development companies&lt;/li&gt;
&lt;li&gt;SaaS productivity tools&lt;/li&gt;
&lt;li&gt;Consumer software platforms&lt;/li&gt;
&lt;li&gt;Digital product marketplaces&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Tools and Technical Services
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Choose .dev&lt;/strong&gt; for businesses serving developers, offering programming tools, or providing technical infrastructure. This extension builds immediate credibility with technical audiences.&lt;/p&gt;

&lt;p&gt;Perfect .dev applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API services and documentation&lt;/li&gt;
&lt;li&gt;Developer tool companies&lt;/li&gt;
&lt;li&gt;Code hosting and collaboration platforms&lt;/li&gt;
&lt;li&gt;Technical consulting services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3rjlnrt90az9tpz6qze.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3rjlnrt90az9tpz6qze.jpg" alt="Grid of 9 domain listings showing .app and .dev domains with pricing, categories, and blue " width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Domain Strategy
&lt;/h2&gt;

&lt;p&gt;The trend toward semantic TLDs represents a fundamental shift in how businesses approach digital identity. &lt;strong&gt;Domains are evolving from simple web addresses into strategic brand assets&lt;/strong&gt; that communicate value propositions before users even visit your site.&lt;/p&gt;

&lt;p&gt;Companies that embrace this evolution gain competitive advantages in search rankings, user perception, and investor appeal. Those clinging to generic .com domains risk being perceived as outdated or unfocused.&lt;/p&gt;

&lt;p&gt;The data supports this shift: when a small island nation can generate $32 million annually from a two-letter domain extension, it's clear that businesses see real value in semantic domain positioning.&lt;/p&gt;

&lt;p&gt;As AI continues to reshape every industry, expect .ai domains to become even more valuable. Similarly, as software development becomes increasingly central to business operations, .dev and .app domains will likely appreciate in both practical utility and market value.&lt;/p&gt;

&lt;p&gt;The question isn't whether semantic TLDs will replace .com — it's how quickly businesses will recognize that their domain extension is now a core component of their competitive positioning strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; The Domain Extension Wars: Why .ai and .app Are Crushing .com&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why Smart Startups Are Ditching .com for .ai and .dev Domains&lt;/li&gt;
&lt;li&gt;The $32M TLD Revolution: How New Domain Extensions Are Changing Tech&lt;/li&gt;
&lt;li&gt;From .com to .ai: The Strategic Domain Shift Reshaping Startups&lt;/li&gt;
&lt;li&gt;The Death of .com? Why Modern TLDs Are the New Business Standard&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;domain-extension-wars-ai-app-crushing-com&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;webdev, startup, branding, domains&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>domains</category>
      <category>branding</category>
    </item>
    <item>
      <title>AI Agent Architecture vs Workflow vs Modularity Know The Difference</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Tue, 23 Jun 2026 11:45:09 +0000</pubDate>
      <link>https://dev.to/julianneagu/ai-agent-architecture-vs-workflow-vs-modularity-knwo-the-difference-5480</link>
      <guid>https://dev.to/julianneagu/ai-agent-architecture-vs-workflow-vs-modularity-knwo-the-difference-5480</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Architecture defines system structure and roles, workflows control execution paths, and modularity enables component reuse. Focus on architecture for consistency issues, workflows for performance problems, and modularity for scaling pain. Start simple, then evolve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Building AI agents that actually work in production requires understanding three distinct but interconnected concepts: architecture, workflow, and modularity. Most teams confuse these layers, leading to systems that demo well but crumble under real users.&lt;/p&gt;

&lt;p&gt;Think of architecture as the wiring in your house, workflows as the electricity flowing through it, and modularity as the replaceable appliances you can swap without rewiring everything. Get these distinctions right, and you'll design cleaner systems that scale predictably.&lt;/p&gt;

&lt;p&gt;The stakes are real. Teams that master these three layers ship faster, debug easier, and collaborate without stepping on each other's code. Those who don't end up rewriting everything when requirements change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is AI Agent Architecture?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51vms95i6syb4bgb46cr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51vms95i6syb4bgb46cr.jpg" alt="Flowchart showing AI agent components: Planner with calendar icon connects to Router with network diagram, which links to Memory with chip icon, Executor with gear icon, and Guardrails with shield ico" width="714" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Architecture is your system's blueprint — the static map that defines who talks to whom, how data flows, and where decisions get made. It's not about the specific steps your agents take; it's about the foundation that makes those steps possible.&lt;/p&gt;

&lt;p&gt;The core roles form a clear division of labor. &lt;strong&gt;The Planner&lt;/strong&gt; breaks high-level goals into actionable tasks. &lt;strong&gt;The Router&lt;/strong&gt; decides which agent or tool handles each task. &lt;strong&gt;The Executor&lt;/strong&gt; does the actual work — calling APIs, processing data, or generating content. &lt;strong&gt;Memory&lt;/strong&gt; preserves context and state across interactions. &lt;strong&gt;Guardrails&lt;/strong&gt; enforce safety policies, access controls, and audit requirements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: Basic agent architecture&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentSystem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskPlanner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AgentRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContextMemory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guardrails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SafetyLayer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;planner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPlan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectAgent&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;nextTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guardrails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Architecture also defines your data paths and decision points. How does information flow from initial request to final response? Where do you transform, validate, or enrich data? What happens when something fails?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clean architecture prevents the most common AI system failures: inconsistent behavior, unclear hand-offs, and security gaps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good architecture centralizes policy enforcement. Instead of scattering safety checks across every agent, you build guardrails once and route everything through them. Role-based access control, PII handling, and audit logging become system-wide properties, supported by &lt;strong&gt;&lt;a href="https://gdprcompliance.dev/blog/conduct-gdpr-compliance-audit-step-step" rel="noopener noreferrer"&gt;structured compliance audit processes&lt;/a&gt;&lt;/strong&gt;, not per-agent responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an AI Workflow?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchlnph258n0wjjrowfb9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fchlnph258n0wjjrowfb9.jpg" alt="Flowchart showing AI workflow steps with icons: Request document, Router node, Research magnifying glass, Summarize text, Review checkmark, Output monitor on dark blue background" width="706" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While architecture is your blueprint, workflow is what happens when you flip the switch. It's the step-by-step execution path that turns user intentions into actual results.&lt;/p&gt;

&lt;p&gt;A typical workflow might look like: User query → Router analyzes intent → Research Agent gathers sources → Summary Agent condenses findings → Quality Reviewer validates output → System returns formatted response.&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;# Example: Debugging a slow workflow&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/research &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "latest AI regulations", "depth": "comprehensive"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--trace-time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workflows come in different patterns. &lt;strong&gt;Linear flows&lt;/strong&gt; move predictably from step A to B to C — easy to test and debug. &lt;strong&gt;Branching flows&lt;/strong&gt; add conditional logic based on content type, user permissions, or quality thresholds. &lt;strong&gt;Parallel flows&lt;/strong&gt; speed things up by running independent tasks simultaneously. &lt;strong&gt;Event-driven flows&lt;/strong&gt; react to triggers, webhooks, or queue messages.&lt;/p&gt;

&lt;p&gt;The workflow layer is where performance becomes visible. You'll see bottlenecks, retry loops, and escalation paths play out in real time. A research workflow that takes 30 seconds usually has one slow step, not thirty slow ones.&lt;/p&gt;

&lt;p&gt;Common workflow problems include: agents waiting unnecessarily for sequential tasks that could run in parallel, retry logic that creates infinite loops when external APIs fail, and handoff points where context gets lost between agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Goes Wrong in Workflows
&lt;/h3&gt;

&lt;p&gt;Here's a real error you might encounter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error: Research agent &lt;span class="nb"&gt;timeout &lt;/span&gt;after 45s
Stack: &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"climate policy 2024"&lt;/span&gt; → router → research_agent → &lt;span class="o"&gt;[&lt;/span&gt;TIMEOUT]
Cause: External API rate limit &lt;span class="o"&gt;(&lt;/span&gt;429&lt;span class="o"&gt;)&lt;/span&gt; → retry loop → eventual &lt;span class="nb"&gt;timeout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix usually involves adding proper retry backoff and parallel source gathering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Instead of sequential API calls&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sources&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;of&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="nx"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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;fetchWithRetry&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="c1"&gt;// Slow!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use Promise.allSettled for parallel fetching&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&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;allSettled&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchWithRetry&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;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&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;Start with simple linear workflows before adding branches or complexity. You can always optimize later once you understand where the bottlenecks actually occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Modularity in AI Systems?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl4ry027iq7g3x33d2t7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl4ry027iq7g3x33d2t7.jpg" alt="Blue diagram showing modular AI system concept with building blocks, arrows pointing to swap, reuse, and isolate functions on dark background" width="694" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modularity treats your AI stack like LEGO blocks. Each component has one clear job and connects to others through well-defined interfaces. When you need to upgrade your summarizer or swap language models, you replace one block without touching the rest.&lt;/p&gt;

&lt;p&gt;The key principle: &lt;strong&gt;one responsibility per module&lt;/strong&gt;. A retriever module only retrieves. A reranker only reranks. A formatter only formats. When each module has a single, clear purpose, the whole system becomes predictable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modular systems let teams ship upgrades in hours instead of months.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Real modularity benefits become obvious during maintenance. Imagine you built a research system six months ago using GPT-3.5. Today you want to upgrade to GPT-4. In a modular system, you swap the language model module and you're done. In a monolithic system, you're hunting through code to find every place that calls the old API.&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;# Modular approach: swap implementations easily
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SummarizerInterface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GPT35Summarizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SummarizerInterface&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# GPT-3.5 implementation
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GPT4Summarizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SummarizerInterface&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# GPT-4 implementation
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# Swap models without changing workflow code
&lt;/span&gt;&lt;span class="n"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GPT4Summarizer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Was: GPT35Summarizer()
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summarizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;summarizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teams collaborate more effectively with clear module boundaries. The retrieval team owns the search module. The safety team owns the guardrails module. The UI team owns the formatting module. Each team can ship improvements without coordinating every change.&lt;/p&gt;

&lt;p&gt;Consider building modules that solve these common needs across multiple workflows. I cover the security implications in detail in &lt;a href="https://visionvix.com/best-ai-agent-security-tools/" rel="noopener noreferrer"&gt;my AI agent security analysis&lt;/a&gt;, but the core insight is that reusable security modules prevent teams from rebuilding authentication and audit logging for every new agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Focus on Each Layer
&lt;/h3&gt;

&lt;p&gt;System problems usually stem from one specific layer. Identifying the root cause helps you fix the right thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on architecture when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agents behave inconsistently across similar requests&lt;/li&gt;
&lt;li&gt;You're duplicating safety checks or access controls&lt;/li&gt;
&lt;li&gt;Teams are stepping on each other's code&lt;/li&gt;
&lt;li&gt;Adding new capabilities requires touching multiple files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Improve workflow when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests take too long or stall unpredictably
&lt;/li&gt;
&lt;li&gt;Users abandon tasks before completion&lt;/li&gt;
&lt;li&gt;You can't easily trace what went wrong&lt;/li&gt;
&lt;li&gt;Similar tasks have wildly different performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Invest in modularity when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upgrades require rewriting large portions of code&lt;/li&gt;
&lt;li&gt;Testing one component breaks others&lt;/li&gt;
&lt;li&gt;Teams can't work independently&lt;/li&gt;
&lt;li&gt;You're rebuilding similar functionality across projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Architecture, Workflow, and Modularity Interconnect
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqaheglr5se30ys932soq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqaheglr5se30ys932soq.jpg" alt="How Architecture, Workflow, and Modularity Interconnect" width="703" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These three layers form a virtuous cycle. Architecture provides the foundation and interfaces. Workflows run on that foundation and expose friction points. Modularity enables you to swap components and extend capabilities without rebuilding everything.&lt;/p&gt;

&lt;p&gt;The cycle looks like this: solid architecture creates clear contracts → workflows reveal bottlenecks and edge cases → modular components let you optimize specific pieces → improved components make workflows faster → better workflows stress-test the architecture → architectural improvements unlock new modular possibilities.&lt;/p&gt;

&lt;p&gt;Teams that master this cycle ship multi-agent systems that actually scale. They add new capabilities by plugging in modules, not by rewriting core logic. They optimize performance by swapping faster components, reducing &lt;strong&gt;&lt;a href="https://coderefactor.dev/blog/reduce-technical-debt-code-refactoring" rel="noopener noreferrer"&gt;technical debt through modular refactoring&lt;/a&gt;&lt;/strong&gt; instead of hunting through monolithic code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Building a Research System
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6fk34rcyl4qxpqh41zj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6fk34rcyl4qxpqh41zj.jpg" alt="Practical Example: From Idea to System" width="699" height="658"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's trace how these concepts work together in a real system. You want to build a tool that researches topics, synthesizes sources, and returns clean, cited answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; Your system needs a Planner to break research goals into searchable questions, a Data Fetcher to pull credible sources, a Summarizer to synthesize evidence, and a Reviewer to enforce citation standards and accuracy. Memory stores context between research steps. Guardrails validate source credibility and prevent harmful outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt; User submits research query → Planner generates search terms → Data Fetcher runs parallel searches → Results get filtered for credibility → Summarizer creates draft with citations → Reviewer checks accuracy and completeness → System returns formatted research brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modularity:&lt;/strong&gt; Each agent is swappable. Upgrade from basic web search to academic database access by replacing the Data Fetcher module. Improve summary quality by swapping the Summarizer. Add fact-checking by plugging in a new Reviewer module.&lt;/p&gt;

&lt;p&gt;When something goes wrong, the layer tells you where to look. Inconsistent citation formats? That's architecture — centralize formatting rules. Slow research? That's workflow — parallelize the search phase. Hard to add new source types? That's modularity — abstract the fetcher interface.&lt;/p&gt;

&lt;p&gt;Start simple with linear workflows and basic modularity. Add branching logic and sophisticated modules as you learn where optimization matters. The goal isn't perfect architecture on day one — it's a system that evolves intelligently as requirements change.&lt;/p&gt;

&lt;p&gt;The teams shipping the most impressive AI systems aren't the ones with the most complex architectures. They're the ones who understand when to focus on structure, when to optimize flow, and when to increase modularity. Master those decisions, and your AI systems will scale from clever demos to dependable products.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; AI Agent Architecture vs Workflow vs Modularity: The Complete Guide to Building Production Systems&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding AI Agent Architecture, Workflows, and Modularity for Production-Ready Systems&lt;/li&gt;
&lt;li&gt;The Three Pillars of AI Agent Development: Architecture, Workflow, and Modularity Explained&lt;/li&gt;
&lt;li&gt;Building Scalable AI Agents: Mastering Architecture, Workflow, and Modularity Design&lt;/li&gt;
&lt;li&gt;From Demo to Production: Understanding AI Agent Architecture, Workflows, and Modular Design&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;ai-agent-architecture-vs-workflow-vs-modularity-guide&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;ai, architecture, tutorial, agents&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>tutorial</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Deployed 44 Live Websites in 10 Minutes With One PowerShell Script (Here's How)</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Sat, 20 Jun 2026 14:27:25 +0000</pubDate>
      <link>https://dev.to/julianneagu/i-deployed-44-live-websites-in-10-minutes-with-one-powershell-script-heres-how-586k</link>
      <guid>https://dev.to/julianneagu/i-deployed-44-live-websites-in-10-minutes-with-one-powershell-script-heres-how-586k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I deployed 44 live websites in a single 10-minute session using a single PowerShell script. Zero manual clicks. Zero dashboard logins. This is how you operate 1,000+ domains as a solo founder without losing your mind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most solo founders hit a wall around product three or four. They talk about building. They don't talk about what happens when you're managing infrastructure for hundreds of live products at once.&lt;/p&gt;

&lt;p&gt;I own 1,000+ premium .app domains, all listed through my &lt;strong&gt;&lt;a href="https://visionvix.com/domain-sales" rel="noopener noreferrer"&gt;domain portfolio &amp;amp; sales overview&lt;/a&gt;&lt;/strong&gt;. Every single one needs to be a live landing page. Every one needs its own deployment, DNS configuration, security headers, sitemap, robots.txt file, and SSL certificate. Do that manually and you're staring at weeks of clicking through GoDaddy dashboards, copy-pasting DNS records one domain at a time, waiting for propagation, verifying each one individually. &lt;/p&gt;

&lt;p&gt;That's not a workflow. That's a trap.&lt;/p&gt;

&lt;p&gt;So I built a pipeline that turns all of it into one command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4hgvf5ndlcnhajib7ia.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4hgvf5ndlcnhajib7ia.jpg" alt="screenshot of browse premium .app domains" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Tool Stack That Makes This Possible
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt; serves as the single source of truth. One monorepo. One subfolder per domain. Every domain folder contains exactly 11 files: the HTML landing page, hero image, Open Graph image, sitemap, robots.txt, manifest, favicons. Push to GitHub and everything downstream updates automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel CLI&lt;/strong&gt; handles all deployments. Not the web dashboard. The command-line interface. Every subfolder becomes its own independent Vercel project. Each deployment takes about 6 seconds. The custom domain gets attached automatically in the same command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GoDaddy API&lt;/strong&gt; manages DNS records. Instead of logging into GoDaddy's dashboard and editing A records one by one, PowerShell calls the REST API directly and writes the record instantly. No forms. No waiting for pages to load.&lt;/p&gt;

&lt;p&gt;One script coordinates all three. That's the entire deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzoeek6czklmfxf1ileb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxzoeek6czklmfxf1ileb.png" alt="Terminal window showing automated deployment script deploying multiple .app domains to Vercel with DNS configuration" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monorepo Structure
&lt;/h2&gt;

&lt;p&gt;Here's what the repo looks like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;plaintext&lt;br&gt;
visionvix-domains/&lt;br&gt;
├── deploy.ps1          ← one script runs everything&lt;br&gt;
├── domains.txt         ← one domain per line&lt;br&gt;
│&lt;br&gt;
├── accessibilityaudit.app/&lt;br&gt;
│   ├── index.html&lt;br&gt;
│   ├── og.jpg&lt;br&gt;
│   ├── hero.webp&lt;br&gt;
│   ├── sitemap.xml&lt;br&gt;
│   ├── robots.txt&lt;br&gt;
│   └── manifest.json&lt;br&gt;
│&lt;br&gt;
├── accountingassistant.app/&lt;br&gt;
│   └── ... same 11 files&lt;br&gt;
│&lt;br&gt;
└── ... one folder per domain&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Simple. Consistent. Scalable. Every domain folder is identical in structure. The only thing that changes is the content inside &lt;code&gt;index.html&lt;/code&gt;. Each landing page is customized for its specific AI tool or service, but the file structure never varies.&lt;/p&gt;

&lt;p&gt;The entire repo is public on GitHub. That's intentional. It's a marketing asset as much as a technical one. Any developer who finds the repo can see the scale immediately: hundreds of domain folders, all consistently structured, all live. It shows what's possible as a solo founder operating with the right tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Script That Does Everything
&lt;/h2&gt;

&lt;p&gt;Here's the core deployment loop. Three API calls. One iteration. Zero manual steps.&lt;/p&gt;

&lt;p&gt;On Windows, the deployment script looks like this:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;powershell&lt;br&gt;
&lt;/code&gt;$domains = Get-Content .\domains.txt&lt;/p&gt;

&lt;p&gt;foreach ($domain in $domains) {&lt;br&gt;
  $slug = $domain -replace '.app$', ''&lt;/p&gt;

&lt;p&gt;# Step 1: Deploy to Vercel (takes ~6 seconds per domain)&lt;br&gt;
  vercel deploy .\$domain --prod --yes --name $slug&lt;/p&gt;

&lt;p&gt;# Step 2: Attach the custom domain and www subdomain&lt;br&gt;
  vercel domains add $domain $slug&lt;br&gt;
  vercel domains add &lt;a href="http://www.$domain" rel="noopener noreferrer"&gt;www.$domain&lt;/a&gt; $slug&lt;/p&gt;

&lt;p&gt;# Step 3: Write the A record to GoDaddy via REST API&lt;br&gt;
  Invoke-RestMethod -Method Put &lt;code&gt;&lt;br&gt;
    -Uri "https://api.godaddy.com/v1/domains/$domain/records/A/@"&lt;/code&gt;&lt;br&gt;
    -Headers $headers `&lt;br&gt;
    -Body '[{"data":"76.76.21.21","ttl":600}]'&lt;/p&gt;

&lt;p&gt;# Step 4: Verify DNS was written correctly&lt;br&gt;
  $rec = Invoke-RestMethod -Method Get &lt;code&gt;&lt;br&gt;
    -Uri "https://api.godaddy.com/v1/domains/$domain/records/A/@"&lt;/code&gt;&lt;br&gt;
    -Headers $headers&lt;br&gt;
  Write-Host "✓ DNS configured: $($rec.data)"&lt;br&gt;
}&lt;code&gt;&lt;br&gt;
&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;That's it. Every domain in the &lt;code&gt;domains.txt&lt;/code&gt; file goes through all four steps automatically, one after another.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;$headers&lt;/code&gt; variable contains the GoDaddy API key and secret, stored in Windows environment variables. They're set once at the system level, available to every PowerShell session, never written to any file, never pushed to GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcydj6awfb2w87ylrcyba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcydj6awfb2w87ylrcyba.png" alt="Side-by-side comparison of cluttered manual dashboard workflow versus streamlined single-command terminal automation" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vercel A Record
&lt;/h3&gt;

&lt;p&gt;The IP address &lt;code&gt;76.76.21.21&lt;/code&gt; is Vercel's production A record. Every domain points there. Vercel's edge network handles routing to the correct project based on the domain name. The TTL is set to 600 seconds (10 minutes), which is low enough to make changes quickly if needed but high enough to avoid excessive DNS queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the CLI Instead of the Dashboard
&lt;/h3&gt;

&lt;p&gt;The Vercel web dashboard works great for one or two projects. It completely breaks down at scale. Clicking through forms, waiting for pages to load, copying project URLs, manually adding domains. That's dozens of clicks per domain, which is exactly the kind of inefficiency solved by &lt;strong&gt;&lt;a href="https://workflowgenerator.dev/blog/top-workflow-automation-tools-small-teams" rel="noopener noreferrer"&gt;top workflow automation tools for small teams.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CLI turns all of that into one command per domain. &lt;code&gt;vercel deploy&lt;/code&gt; pushes the code, builds it, and returns a production URL. &lt;code&gt;vercel domains add&lt;/code&gt; attaches the custom domain. Both commands are fully scriptable. That's what makes automation possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers After 44 Deployments
&lt;/h2&gt;

&lt;p&gt;The first run was 5 domains. Then 38 more. Then the full 44 batch, all live, all verified, all returning HTTP 200.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;44 domains deployed in a single session. 6 seconds average deploy time per domain. 0 manual clicks required. 100% success rate.&lt;/p&gt;
&lt;/blockquote&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;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Domains deployed in first session&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average deploy time per domain&lt;/td&gt;
&lt;td&gt;~6 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual clicks required&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS propagation time&lt;/td&gt;
&lt;td&gt;5-30 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total time from script to live&lt;/td&gt;
&lt;td&gt;Under 10 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 200 success rate&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When I ran the verification loop at the end, checking every domain for a 200 status code, every single one came back green. No failures. No retries. No manual fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Headers Built Into Every Domain
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1lnmffgovtc0lcjoiw0d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1lnmffgovtc0lcjoiw0d.jpg" alt="screenshot of Website audit AI Agent" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every domain automatically gets a full set of security headers via a vercel.json file that lives inside each domain folder. No extra deployment steps. No separate configuration, and can be validated with a &lt;strong&gt;&lt;a href="https://websiteaudit.dev/" rel="noopener noreferrer"&gt;website security audit tool&lt;/a&gt;&lt;/strong&gt;. The headers deploy with the site. &lt;/p&gt;

&lt;p&gt;Here's what every domain gets out of the box:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "headers": [&lt;br&gt;
    {&lt;br&gt;
      "source": "/(.*)",&lt;br&gt;
      "headers": [&lt;br&gt;
        {&lt;br&gt;
          "key": "Content-Security-Policy",&lt;br&gt;
          "value": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "key": "Strict-Transport-Security",&lt;br&gt;
          "value": "max-age=63072000; includeSubDomains; preload"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "key": "X-Frame-Options",&lt;br&gt;
          "value": "DENY"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "key": "X-Content-Type-Options",&lt;br&gt;
          "value": "nosniff"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "key": "Referrer-Policy",&lt;br&gt;
          "value": "strict-origin-when-cross-origin"&lt;br&gt;
        },&lt;br&gt;
        {&lt;br&gt;
          "key": "Permissions-Policy",&lt;br&gt;
          "value": "camera=(), microphone=(), geolocation=(), payment=()"&lt;br&gt;
        }&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  ]&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content-Security-Policy&lt;/strong&gt; blocks malicious scripts and injection attacks. &lt;strong&gt;Strict-Transport-Security&lt;/strong&gt; forces HTTPS for 2 years and covers all subdomains. &lt;strong&gt;X-Frame-Options: DENY&lt;/strong&gt; prevents clickjacking. &lt;strong&gt;X-Content-Type-Options&lt;/strong&gt; stops MIME type sniffing. &lt;strong&gt;Referrer-Policy&lt;/strong&gt; controls how much referrer data leaks. &lt;strong&gt;Permissions-Policy&lt;/strong&gt; disables camera, microphone, geolocation, and payment APIs.&lt;/p&gt;

&lt;p&gt;Static assets like images and fonts are cached for a full year. The HTML is never cached, so users always get the latest version instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why These Architecture Decisions Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why a Public GitHub Monorepo?
&lt;/h3&gt;

&lt;p&gt;Security is handled without secrets ever touching the repo. GoDaddy API keys live in Windows environment variables only. Set once at the system level. Available to every PowerShell session. Never written to any file. Never pushed to GitHub.&lt;/p&gt;

&lt;p&gt;The repo itself is public because it's a marketing asset. Developers who find it see the scale immediately. Hundreds of domain folders. All consistently structured. All live. It shows what's possible as a solo founder with the right automation in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Vercel Over the Alternatives?
&lt;/h3&gt;

&lt;p&gt;The CLI is fully scriptable. That's the only reason that matters at this scale. Netlify and Cloudflare Pages both have solid products, but Vercel's CLI lets you deploy a subfolder, attach a domain, and get a production URL in a single command. That's what makes automation possible.&lt;/p&gt;

&lt;p&gt;I can run the same script on 5 domains or 500 domains. The process doesn't change. The complexity doesn't increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why GoDaddy API Instead of Manual DNS?
&lt;/h3&gt;

&lt;p&gt;There is no "manual" at 600 domains. Logging into GoDaddy's dashboard and editing A records one by one would take days. The GoDaddy REST API accepts a PUT request with the new A record value and applies it instantly. PowerShell's &lt;code&gt;Invoke-RestMethod&lt;/code&gt; makes the call in one line. The entire DNS step takes under a second per domain.&lt;/p&gt;

&lt;p&gt;For readers who want to dive deeper into the strategy behind premium .app domains, I covered the positioning and branding implications in &lt;a href="https://visionvix.com/what-is-ai-domain/" rel="noopener noreferrer"&gt;my earlier post on AI domain strategy&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next: 1,000 Domains by Next Week
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa14ombqtnur1yko9cxo.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffa14ombqtnur1yko9cxo.jpeg" alt="GitHub repository browser showing organized monorepo structure with domain folders each containing identical file sets&lt;br&gt;
" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;44 domains is the proof of concept. The pipeline works. Now it's just execution at scale.&lt;/p&gt;

&lt;p&gt;The plan for the next 7 days:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy the remaining 950+ domains&lt;/strong&gt; using the same script&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index all pages with Google Search Console&lt;/strong&gt; via sitemap submission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track success rates&lt;/strong&gt; for deployment, DNS propagation, and indexing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor performance&lt;/strong&gt; across the entire domain portfolio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate on the landing page template&lt;/strong&gt; based on traffic patterns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The script doesn't care if it's running on 50 domains or 500. The time per domain stays constant. The manual effort stays at zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Point
&lt;/h2&gt;

&lt;p&gt;This isn't really about domains. It's about treating infrastructure as code. One person can operate at enterprise scale when every manual process is replaced with an API call.&lt;/p&gt;

&lt;p&gt;The traditional approach to scaling infrastructure is to hire people. You need someone to manage DNS. Someone to handle deployments. Someone to configure security. Someone to monitor uptime.&lt;/p&gt;

&lt;p&gt;The alternative is to script everything once and run it a thousand times.&lt;/p&gt;

&lt;p&gt;I'm not managing 1,000 domains. I'm managing one script that manages 1,000 domains. That's the difference between a job and a system.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; I Deployed 44 Live Websites in 10 Minutes With One PowerShell Script (Here's How)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How I Deploy 1,000+ Premium Domains as a Solo Founder Without Losing My Mind&lt;/li&gt;
&lt;li&gt;Zero-Click Deployment Pipeline: Managing Hundreds of Live Domains With GitHub, Vercel &amp;amp; GoDaddy API&lt;/li&gt;
&lt;li&gt;From Manual Hell to One-Command Deploy: Automating 1,000+ Domain Infrastructure&lt;/li&gt;
&lt;li&gt;The Three-Tool Stack That Let Me Deploy 44 Websites in a Single 10-Minute Session&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;i-deployed-44-live-websites-in-10-minutes-with-one-powershell-script&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;devops, automation, productivity, domains&lt;/p&gt;

</description>
      <category>automation</category>
      <category>webdev</category>
      <category>domains</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building React Components: From JSX to Production-Ready Code</title>
      <dc:creator>Julian Neagu</dc:creator>
      <pubDate>Fri, 19 Jun 2026 13:40:18 +0000</pubDate>
      <link>https://dev.to/julianneagu/building-react-components-from-jsx-to-production-ready-code-3chf</link>
      <guid>https://dev.to/julianneagu/building-react-components-from-jsx-to-production-ready-code-3chf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; React components are reusable UI building blocks that combine structure, logic, and styling in isolated modules. Function components with Hooks are now the standard, replacing classes for cleaner, more maintainable code that scales across modern frameworks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React components form the backbone of every React application. They break your user interface into small, logical pieces that handle their own structure, styling, and behavior independently. This modular approach transforms how developers build applications—making code more maintainable, bugs easier to isolate, and features faster to ship.&lt;/p&gt;

&lt;p&gt;The numbers tell the story of React's dominance. According to JetBrains' 2024 Developer Ecosystem Report, &lt;strong&gt;57% of JavaScript developers use React as their primary library&lt;/strong&gt;. On NPM, React now pulls over 20 million weekly downloads, cementing its position as the most widely adopted UI framework globally.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React exceeds 20 million weekly downloads on NPM, confirming its position as the most adopted UI framework in the world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa85nxi155b6fyp4vwhcs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa85nxi155b6fyp4vwhcs.jpg" alt="This infographic presents the anatomy of a React component using a dark blue background for contrast. It divides content into four panels labeled Props, State, Hooks, and Return, each with icons and brief code examples. The design highlights how React components handle data, logic, and rendering in a clean, modern layout." width="687" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Component Work
&lt;/h2&gt;

&lt;p&gt;A React component is fundamentally a JavaScript function that returns JSX—a blend of HTML-like syntax and JavaScript logic. Think of it as a custom HTML element that you define once and reuse anywhere.&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;Welcome&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&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="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple component takes a &lt;code&gt;name&lt;/code&gt; prop and renders a personalized greeting. You can use it multiple times with different data:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sarah&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mike&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each instance behaves identically but displays unique content based on the props you pass in. This pattern—write once, use everywhere—drives React's efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSX and TSX: The Language of Components
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JSX Explained
&lt;/h3&gt;

&lt;p&gt;JSX (JavaScript XML) lets you write HTML-like code directly in JavaScript. Behind the scenes, React transforms this syntax into function calls that create virtual DOM elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&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="s1"&gt;Welcome to React&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSX makes component code readable and intuitive, especially for developers familiar with HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  TSX and Type Safety
&lt;/h3&gt;

&lt;p&gt;TSX extends JSX with TypeScript's type system. It catches errors before your code runs and provides better autocomplete in editors.&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;disabled&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="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;90.6% of frontend developers now use TypeScript, making TSX the standard format for professional React development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Component Architecture Breakdown
&lt;/h2&gt;

&lt;p&gt;Every React component has four essential parts that work together to create interactive UI elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Props: External Input
&lt;/h3&gt;

&lt;p&gt;Props are data passed from parent to child components. They define what information a component receives and how it should behave.&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductCard&lt;/span&gt; 
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Digital Camera&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;299&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
  &lt;span class="nx"&gt;inStock&lt;/span&gt;&lt;span class="o"&gt;=&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="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Props make components flexible. The same &lt;code&gt;ProductCard&lt;/code&gt; component can display any product by changing the props you pass to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  State: Internal Data
&lt;/h3&gt;

&lt;p&gt;State holds data that can change during a component's lifetime. When state updates, React automatically re-renders the component with the new data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;State enables interactivity—user actions trigger state changes, which update the UI immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hooks: Lifecycle Management
&lt;/h3&gt;

&lt;p&gt;Hooks like &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, and &lt;code&gt;useRef&lt;/code&gt; give function components access to React's lifecycle features without writing class components.&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="nf"&gt;useEffect&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Count: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&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;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Return: UI Output
&lt;/h3&gt;

&lt;p&gt;The return statement defines what the component renders to the screen. It must return a single JSX element (or fragment).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq22egwzxbst8d4zzbag.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcq22egwzxbst8d4zzbag.jpg" alt="This infographic compares React Function Components and Class Components. It displays five key aspects—definition, state handling, side effects, syntax, and recommended use—in a two-column format. Each point uses icons and brief text to show that Function Components use hooks and simple syntax, while Class Components rely on lifecycle methods and are suited for legacy projects." width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Function vs Class Components: The Modern Standard
&lt;/h2&gt;

&lt;p&gt;React originally used class components, but the introduction of Hooks in 2019 shifted the entire ecosystem toward function components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Class Component (Legacy):&lt;/strong&gt;&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;class&lt;/span&gt; &lt;span class="nc"&gt;Welcome&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;render&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&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;&lt;strong&gt;Function Component (Modern):&lt;/strong&gt;&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;Welcome&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;},&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function components require less boilerplate, avoid this binding issues, and integrate naturally with modern tooling. The React team now recommends function components for all new development, especially as teams move toward &lt;strong&gt;&lt;a href="https://coderefactor.dev/blog/clean-code-practices-improve-code-maintainability" rel="noopener noreferrer"&gt;cleaner code maintainability practices.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power of Reusability
&lt;/h2&gt;

&lt;p&gt;React's component model promotes code reuse through composition. Instead of copying similar code, you create one component and customize it with props.&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Save&lt;/span&gt; &lt;span class="nx"&gt;Changes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Cancel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Delete&lt;/span&gt; &lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;Button&lt;/code&gt; component handles three different use cases by changing props. This approach keeps your codebase smaller and more consistent.&lt;/p&gt;

&lt;p&gt;A Stack Overflow 2024 Developer Survey found that &lt;strong&gt;over 70% of React developers cite "reusability" as the primary reason for adopting React&lt;/strong&gt; in their projects. When teams can build once and reuse everywhere, development velocity increases dramatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over 70% of React developers cite "reusability" as the primary reason for adopting React in their projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Universal Building Blocks Across Frameworks
&lt;/h2&gt;

&lt;p&gt;React components aren't limited to basic React apps. They power the entire modern JavaScript ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js&lt;/strong&gt; uses components to define pages (&lt;code&gt;page.tsx&lt;/code&gt;) and layouts (&lt;code&gt;layout.tsx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gatsby&lt;/strong&gt; generates static sites from React components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remix&lt;/strong&gt; renders components server-side for faster performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo&lt;/strong&gt; builds mobile apps using React Native components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Next.js 14's App Router, every file in the &lt;code&gt;app/&lt;/code&gt; directory exports a React component. This structure automatically converts your file system into routes—no configuration required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwev07tiffoq869ndmb6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwev07tiffoq869ndmb6.jpg" alt="Visionvix domain marketplace" width="799" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood: JSX Compilation
&lt;/h2&gt;

&lt;p&gt;React doesn't understand JSX natively. Build tools like Babel or SWC compile JSX into JavaScript before it runs in browsers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before React 17:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&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="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;React 17+ (New JSX Transform):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_jsx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This modern transform eliminates the need to import React in every file and makes builds faster. Most developers never see this compiled output, but understanding it helps debug complex JSX issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streamlined Development with Modern Tooling
&lt;/h2&gt;

&lt;p&gt;For teams working on complex React projects, automation tools can significantly speed up development workflows. When migrating from basic React apps to production-grade Next.js structures, manual conversion becomes time-consuming and error-prone.&lt;/p&gt;

&lt;p&gt;Modern development practices emphasize consistency—from file organization to code formatting. Teams often spend hours manually restructuring components, organizing folders, and ensuring proper TypeScript types when scaling projects.&lt;/p&gt;

&lt;p&gt;I covered similar automation patterns in &lt;strong&gt;&lt;a href="https://visionvix.com/cursor-best-practices/" rel="noopener noreferrer"&gt;my cursor best practices guide&lt;/a&gt;&lt;/strong&gt;, where consistent tooling and formatting become critical as codebases grow beyond simple prototypes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Design Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keep Components Focused
&lt;/h3&gt;

&lt;p&gt;Each component should handle one responsibility. A &lt;code&gt;UserProfile&lt;/code&gt; component manages user data display, while a separate &lt;code&gt;EditUserForm&lt;/code&gt; handles user data updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Descriptive Names
&lt;/h3&gt;

&lt;p&gt;Component names should immediately convey purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ProductCard&lt;/code&gt; instead of &lt;code&gt;Card&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SignupForm&lt;/code&gt; instead of &lt;code&gt;Form&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PaymentButton&lt;/code&gt; instead of &lt;code&gt;Button&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Embrace Composition
&lt;/h3&gt;

&lt;p&gt;Build complex interfaces by combining simple components rather than creating monolithic components with many features.&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardHeader&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product Details&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardBody&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductImage&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&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;image&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductInfo&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&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="nx"&gt;price&lt;/span&gt;&lt;span class="o"&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;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardBody&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardFooter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AddToCartButton&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardFooter&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type Everything
&lt;/h3&gt;

&lt;p&gt;Add TypeScript interfaces for all props to catch errors early and improve developer experience:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ProductCardProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;onAddToCart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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;h2&gt;
  
  
  Common Component Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mixing Concerns:&lt;/strong&gt; Don't handle data fetching, business logic, and UI rendering in the same component. Separate these responsibilities into different layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prop Drilling:&lt;/strong&gt; Avoid passing props through multiple component layers. Use React Context or state management libraries for deeply nested data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring Keys:&lt;/strong&gt; Always provide unique &lt;code&gt;key&lt;/code&gt; props when rendering lists to help React optimize re-renders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutating Props:&lt;/strong&gt; Never modify props directly. Treat them as read-only data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overusing useEffect:&lt;/strong&gt; Many side effects can be handled in event handlers instead of effects, leading to simpler code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Component Development
&lt;/h2&gt;

&lt;p&gt;React continues evolving with features like Server Components and Concurrent Rendering. However, the core principles of component-based architecture remain constant: modularity, reusability, and clear separation of concerns.&lt;/p&gt;

&lt;p&gt;Components represent more than just a React pattern, they're a fundamental shift in how we think about building user interfaces. By breaking complex UIs into simple, composable pieces, React components support &lt;strong&gt;&lt;a href="https://htmlgenerator.dev/blog/ai-html-editor-beginners-quick-start" rel="noopener noreferrer"&gt;more maintainable AI-powered development workflows&lt;/a&gt;&lt;/strong&gt; and make frontend development more predictable.&lt;/p&gt;

&lt;p&gt;The next time you build a React application, remember that every component is an opportunity to create something reusable. Start small, think modular, and let composition guide your architecture decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Publishing Kit — Dev.to
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Title Options (5)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Selected:&lt;/strong&gt; Building React Components: From JSX to Production-Ready Code&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React Components Explained: The Complete Guide to Modern UI Development&lt;/li&gt;
&lt;li&gt;Mastering React Components: JSX, Props, and Component Architecture&lt;/li&gt;
&lt;li&gt;React Component Fundamentals: Building Scalable UI with Modern Patterns&lt;/li&gt;
&lt;li&gt;The Developer's Guide to React Components: From Basics to Best Practices&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Slug
&lt;/h3&gt;

&lt;p&gt;building-react-components-jsx-production-ready-code&lt;/p&gt;

&lt;h3&gt;
  
  
  Tags
&lt;/h3&gt;

&lt;p&gt;react, webdev, javascript, components&lt;/p&gt;

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