<?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: Harshit Satyaseel</title>
    <description>The latest articles on DEV Community by Harshit Satyaseel (@meharshit).</description>
    <link>https://dev.to/meharshit</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%2F4120351%2F027fcca2-4052-4753-8cd3-8093c2606e76.png</url>
      <title>DEV Community: Harshit Satyaseel</title>
      <link>https://dev.to/meharshit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meharshit"/>
    <language>en</language>
    <item>
      <title>Database Replication vs Sharding: What They Are, When to Use Them, and How They Work Together</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Mon, 21 Sep 2026 10:30:09 +0000</pubDate>
      <link>https://dev.to/meharshit/database-replication-vs-sharding-what-they-are-when-to-use-them-and-how-they-work-together-g7p</link>
      <guid>https://dev.to/meharshit/database-replication-vs-sharding-what-they-are-when-to-use-them-and-how-they-work-together-g7p</guid>
      <description>&lt;p&gt;Your database just crashed, and it was serving a million users. Their accounts, their orders, their data all gone. How do you get it back?&lt;/p&gt;

&lt;p&gt;You would get it back if you had a copy of that data sitting somewhere else and that's exactly why techniques like &lt;strong&gt;Replication&lt;/strong&gt; and &lt;strong&gt;Sharding&lt;/strong&gt; exist. They solve different problems and work differently. Understanding when to use which one is something every developer and DevOps engineer should know.&lt;/p&gt;

&lt;p&gt;Let's break them down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Database Replication?
&lt;/h2&gt;

&lt;p&gt;Replication is about making copies. You take your entire database and copy it across multiple machines (say, servers) running somewhere in the cloud or on-premises, so if one machine goes down, another one already has everything and can take over.&lt;/p&gt;

&lt;p&gt;Here's how it works in practice. &lt;/p&gt;

&lt;p&gt;Say you have one database machine running in the cloud, handling all the reads and writes for your application. That single machine is doing everything. Every time a user creates an account, places an order, or loads a page, the request hits that one server.&lt;/p&gt;

&lt;p&gt;With replication, you copy that entire database to two or three other machines. Now you have multiple machines, and all of them hold the same data. You make one machine the primary, and it handles all the write operations like inserts, updates, and deletes. The other machines are called replicas, and their job is to handle read requests. When your application needs to fetch data, those read requests go to the replicas instead of all hitting the primary. The primary focuses on writes; the replicas take care of reads.&lt;/p&gt;

&lt;p&gt;Now here's the real reason replication matters. If your primary machine crashes, a replica already has all the data, so it takes over right away. Your application keeps running, and users don't even notice that something went off. This ability to survive a failure without losing data or going offline is called &lt;strong&gt;fault tolerance&lt;/strong&gt;, and it's one of the core reasons why replication exists.&lt;/p&gt;

&lt;p&gt;Replication also gives you simpler backups. Since replicas hold a full copy of the data, you can take backups from a replica without putting any load on the primary server. In systems where read traffic is much higher than write traffic, like e-commerce product pages or content platforms, spreading reads across replicas makes the whole system faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where replication hits a wall
&lt;/h3&gt;

&lt;p&gt;Replication has a limit that becomes obvious as your application grows. Every write still goes through that one primary machine. No matter how many replicas you add, they only handle reads. So as your write traffic increases- more users signing up, more orders being placed, more data being updated- that primary server becomes a bottleneck. All the write pressure lands on one machine.&lt;/p&gt;

&lt;p&gt;You can try upgrading the primary by adding more RAM, a faster CPU, or a bigger disk, and we call this vertical scaling. But those upgrades get expensive fast. A machine with double the power doesn't cost double the money; it costs significantly more. And at some point, there simply is no bigger machine you can buy. You hit the hardware ceiling.&lt;/p&gt;

&lt;p&gt;On top of that, every replica stores a full copy of the entire dataset. If your database is, say, 5 terabytes, and you have three replicas, you're paying for 15 terabytes of storage just to hold the same data three times. Your writes can't scale, your storage costs keep multiplying, and upgrades only get more expensive. That's the issue replication runs into.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is database sharding?
&lt;/h2&gt;

&lt;p&gt;Sharding takes a completely different approach. Instead of copying the same data everywhere, sharding breaks the big data into smaller pieces and distributes each piece across different machines. Each piece is called a shard, and each machine holds only its portion of the total dataset.&lt;/p&gt;

&lt;p&gt;Say your database has a users table with 10 million records. With sharding, you could split it so that users with IDs 1 through 5 million live on one machine, and users with IDs 5,000,001 through 10 million live on another. Each machine now stores and processes only half the data.&lt;/p&gt;

&lt;p&gt;The field you use to decide which data goes where is called the shard key. In our example, the user ID is the shard key. Choosing the right shard key matters a lot — a bad choice can lead to one shard getting most of the traffic while others sit idle, which defeats the whole purpose.&lt;/p&gt;

&lt;p&gt;Because each machine deals with less data, reads are &lt;strong&gt;faster&lt;/strong&gt;; each shard only searches through its portion instead of scanning the full dataset. Writes benefit even more, because the write load is now spread across multiple machines instead of a single primary handling everything.&lt;/p&gt;

&lt;p&gt;Since shards work independently, they can process reads and writes at the same time, in parallel. This is what gives sharding its real power. When you need more capacity, you just add another machine and redistribute some data. This way of scaling by adding more machines instead of upgrading one is called &lt;strong&gt;horizontal scaling&lt;/strong&gt;, and unlike vertical scaling, it doesn't have a ceiling. You can keep adding machines as your data grows.&lt;/p&gt;

&lt;p&gt;Sharding is also more cost-efficient at scale. Instead of paying for one massive, expensive server, you're using multiple smaller, cheaper machines that together handle more than any single machine ever could.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trade-offs of sharding
&lt;/h3&gt;

&lt;p&gt;Sharding isn't free of problems. It introduces complexity that replication doesn't have. The biggest one is cross-shard queries. If a query needs data that lives on two different shards, say you need information about user 100 and user 6 million in the same request, the system has to talk to both machines, fetch the data separately, and combine the results. These queries are slower and harder to optimise.&lt;/p&gt;

&lt;p&gt;In databases like MongoDB, a component called mongos acts as a query router. Your application talks to mongos, and mongos figures out which shard has the data and routes the request. For queries that hit a single shard, this is fast. For queries that span multiple shards, there's overhead.&lt;/p&gt;

&lt;p&gt;There's also the matter of fault tolerance. If a shard goes down, the data on that shard becomes unavailable. Unlike replication, where a copy can step in immediately, sharding by itself doesn't give you that safety net. You would have to recover the data from a backup, and until then, that portion of your system is down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using both together: How production systems actually work
&lt;/h2&gt;

&lt;p&gt;This is why real production systems don't choose one over the other. They use both. The standard approach is to shard your database for scale, and then replicate each shard for fault tolerance. Each shard becomes its own replica set, a primary and two or more replicas holding copies of that shard's data.&lt;/p&gt;

&lt;p&gt;MongoDB's production architecture is a textbook example of this pattern. A MongoDB sharded cluster has three components working together:&lt;/p&gt;

&lt;p&gt;Shards hold the actual data. Each shard is deployed as a replica set, so every piece of data has copies for fault tolerance. If the primary of any shard goes down, one of its replicas is promoted automatically.&lt;/p&gt;

&lt;p&gt;Config servers store metadata about the cluster, which data ranges live on which shard, how chunks are distributed, and where to route each request. Config servers are also deployed as a replica set for reliability. Mongos is the query router. Your application connects to mongos instead of directly to shards. mongos checks the config servers to figure out which shard holds the data, then routes the query there. If the query spans multiple shards, mongos handles the fan-out and merges the results.&lt;/p&gt;

&lt;p&gt;This architecture gives you horizontal scaling from sharding and fault tolerance from replication. Each shard handles its portion of the data, and each shard's replica set makes sure that data survives failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;p&gt;Not every application needs sharding. In fact, most applications start with replication and never need to go further.&lt;/p&gt;

&lt;p&gt;Start with replication when your application is read-heavy, and your dataset fits comfortably on a single machine. A well-configured replica set can handle tens of thousands of reads per second, and it gives you fault tolerance and simpler backups with minimal operational complexity.&lt;/p&gt;

&lt;p&gt;Consider sharding when your dataset has grown beyond what one machine can store, your write volume exceeds what a single primary server can handle, or you need to distribute data geographically across regions. Sharding adds real operational complexity, shard key design, query routing, and rebalancing, so it should be a response to an actual scaling problem, not a precaution.&lt;/p&gt;

&lt;p&gt;Use both when you need scale and fault tolerance together, which is the case for most production systems handling large datasets. Shard for distribution, replicate each shard for safety.&lt;/p&gt;




&lt;p&gt;Replication and sharding solve different problems, and understanding that difference is what separates a system that scales well from one that breaks under pressure.&lt;/p&gt;

&lt;p&gt;Replication copies your entire database across machines. It gives you fault tolerance, faster reads, and simpler backups and is built for keeping your data safe and your application running.&lt;/p&gt;

&lt;p&gt;Sharding splits your data into pieces across machines. It gives you horizontal scaling, parallel processing, and cost efficiency. It's built for handling data and traffic that outgrow a single server.&lt;/p&gt;

</description>
      <category>database</category>
      <category>cloud</category>
      <category>systemdesign</category>
      <category>howtofix</category>
    </item>
    <item>
      <title>Self-Improving Docs, Part 1: Automatically Turning Documentation Gaps Into Pull Requests</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Fri, 18 Sep 2026 08:28:01 +0000</pubDate>
      <link>https://dev.to/meharshit/self-improving-docs-part-1-automatically-turning-documentation-gaps-into-pull-requests-2249</link>
      <guid>https://dev.to/meharshit/self-improving-docs-part-1-automatically-turning-documentation-gaps-into-pull-requests-2249</guid>
      <description>&lt;p&gt;This blog is &lt;strong&gt;Part 1&lt;/strong&gt; of my series, &lt;strong&gt;Self-Improving Docs&lt;/strong&gt;: Turning Gaps Into Pull Requests, on how I run documentation as a self-updating system.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, I will go deeper into the docs assistant itself: how readers ask questions, how we handle feedback, and what we learned while building this experience. In &lt;strong&gt;Part 3&lt;/strong&gt;, I will talk about the other half of the same problem, which is making the docs usable for coding agents through things like indexes, skills, and packaging. This post is about how I close the docs gap when readers fail to get a right answer.&lt;/p&gt;




&lt;p&gt;Let’s start with the old process: before I built automation, the feedback loop was slow because it involved a lot of dependencies. Some readers hit an outdated page, which usually opened a ticket to a support engineer in Slack, and eventually the product team sent that message back to me. I used to fix the wrong doc page, publish the new one, and move on. That still happens, but what changed is the speed and the audience.&lt;/p&gt;

&lt;p&gt;Now, AI agents read documentation a lot and differently from humans. A developer who opens an outdated page can notice the version looks wrong, ask a colleague, or check a changelog. An agent that lands on the same page does not usually pause and ask whether the page is correct; instead, it treats the docs as instructions and keeps going from that incorrect information. That is why outdated documentation stopped feeling like only an editorial problem and started feeling like a system problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt; recently published survey findings that matched what I was already seeing in my own work. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A large share of teams still take a week or longer for a product change to show up in docs. Only about a quarter say it usually lands the same day. At the same time, most teams say AI agents now draft documentation updates for them, but only a small group lets those agents publish without a human in the middle. The interesting part is not the exact percentages. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, the interesting part is the pattern: people know documentation cannot keep up with product changes by writing and updating alone, and they are using agents to draft, while still wanting a human to decide what is true. That is the same place I ended up.&lt;/p&gt;

&lt;p&gt;The problem was not &lt;strong&gt;&lt;em&gt;“we need a chatbot”&lt;/em&gt;&lt;/strong&gt;. A lot of conversations about AI and docs start with chat in my chat assistant. I built a docs assistant on our developer site, and readers use it every day. But chat by itself does not close the loop of missing and incorrect information. If the assistant cannot answer, and that failure disappears into a log nobody reads, you only built a nicer search box; that’s it. Well, I wanted it to know what’s wrong and self-update the docs. I wanted to understand: when the docs assistant cannot answer, or when a reader says the docs need an update, does that become work in the documentation repo?&lt;/p&gt;

&lt;p&gt;If the answer is no, the bot is only an answer-giving machine and is not helping me close the docs gap loop. If the answer is yes, the bot becomes part of how documentation self-improves. So I built a system that self-improves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “closing the loop” means in real documentation
&lt;/h2&gt;

&lt;p&gt;When I say closing the feedback loop, I mean this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A reader asks a question on the docs site.&lt;/li&gt;
&lt;li&gt;The assistant answers from the documentation.&lt;/li&gt;
&lt;li&gt;If the answer looks missing, incomplete, or wrong enough that readers flag it, I capture that information as a data point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This captured data can be used to open an automated documentation job that drafts a change and creates a pull request for me. The idea is that a human still reviews the PR and decides what to merge, but the hunting and getting feedback directly from the doc site is an insanely fast way to improve documentation.&lt;/p&gt;

&lt;p&gt;I also capture feedback that never reaches the “not found” path. Readers can tag answers as helpful or not helpful, mark that something needs a doc update, report a bug, or send free-form feedback into Slack. Those signals matter because not every gap looks like a wrong answer. Sometimes the assistant answers confidently, and the reader still knows something is off. So I ended up with two kinds of input into the same system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic gap detection&lt;/strong&gt; from the assistant response itself, when the reply looks like “not found” or only partial context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human feedback from tags&lt;/strong&gt;, escalation, and the feedback form; both of them are useful. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automatic detection catches problems left out, and human feedback catches “the answer was there, but it was not good enough.”&lt;/p&gt;

&lt;p&gt;If I draw the whole thing, it will look like this:&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%2Fffz9twksm38f5v3eyju8.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%2Fffz9twksm38f5v3eyju8.png" alt="system diagram" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important line in that diagram is the last one. The draft is not published. The &lt;strong&gt;PR&lt;/strong&gt; is where I decide what is true and what needs to get merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the auto-update mechanism works
&lt;/h2&gt;

&lt;p&gt;The idea is simple. After the docs assistant answers, I ask one question: did the docs actually cover what the readers asked? I do not call a separate success API for that; instead, I read the answer itself. If it sounds like the topic is missing from the generated answer, or the model is saying it only has partial context, I treat that as a documentation gap.&lt;/p&gt;

&lt;p&gt;When that happens, the system automatically sends the gap details to my backend with some information such as the reader’s question, the page they were on, and why I think the docs failed. From there, the backend starts a &lt;strong&gt;Mintlify agent job&lt;/strong&gt; with that context.&lt;/p&gt;

&lt;p&gt;The agent’s job is not only to refresh the live site. Its job is to draft the missing documentation, open a branch in the docs repo, and create a pull request. That PR is what I review. If the draft is good, I merge it, and if it needs edits, I edit and fix the missing part of it. If I feel that the gap is not a real docs gap, I close the PR and move on.&lt;/p&gt;

&lt;p&gt;So the mechanism is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Answer comes back
  → Detect gap from the answer text
  → Send question + page + reason to backend
  → Mintlify agent drafts a docs change
  → PR opens in the docs repo
  → Human reviews and merges
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In code, the important part is the agent job call. Once any doc gap is detected, the backend asks Mintlify to draft from the failed question:&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`https://api.mintlify.com/v1/agent/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/job`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;adminApiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`doc-gap-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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="na"&gt;asDraft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;`User asked: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;question&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="s2"&gt;`Page: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This was not found in the docs. Please add or expand documentation.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The assistant had only partial context. Please add or expand documentation where relevant.&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;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Message-Id&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;That is the whole auto-update mechanism. A failed answer becomes context, and that context becomes a draft PR. Tech writers can still decide what ships.&lt;/p&gt;

&lt;p&gt;What I have noticed is that most teams are fine letting agents draft documentation updates, but they still want a human deciding what gets published. That matches how I run it. The agent can open the PR, but the PR is not live docs yet. Someone still has to read the change, check whether it is accurate, and only then merge it. On a multi-product developer site like mine, that review step matters a lot, because one wrong sentence in the wrong product section is enough to break an integration for developers who use our docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback is important
&lt;/h2&gt;

&lt;p&gt;If I only talk about auto triggers, then this story is incomplete.&lt;/p&gt;

&lt;p&gt;On my docs site, readers also send feedback deliberately. Sometimes they use tag chips under an answer, or they escalate to support when the assistant cannot help. They can also open the feedback option and write what is missing in their own words. Those messages go to Slack and into logging, so that I can see the exact wording instead of a vague “someone was unhappy.”&lt;/p&gt;

&lt;p&gt;If I put the feedback mechanism next to the automatic one, it looks like this:&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%2Fekr32q5nj8e430lya66n.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%2Fekr32q5nj8e430lya66n.png" alt="process" width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This matters because documentation gaps rarely arrive as one clean ticket. Most of the time it shows up as the same question asked again, an answer that only covers half of what the readers needed, or a short note like “this page assumes I already know X.” If that signal stays buried in chat history, nothing happens with it. But when the same note reaches Slack or a sheet with the page URL and the original question attached, it becomes useful data that a docs team can actually use for better documentation updates.&lt;/p&gt;

&lt;p&gt;Think of feedback and auto triggers as two doors into the same room. One door is opened by the system when the answer looks empty. The other door is opened by the reader when the answer looks wrong or incomplete, and both must lead to documentation work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this feels different from old feedback process
&lt;/h2&gt;

&lt;p&gt;In many companies and even in the company I work for, the older feedback mechanism depended on someone deciding the docs were worth a ticket. That still happens, and it is still valuable. What changed with a docs assistant is that the failure can be observed at the moment of asking and can be fixed automatically without pulling in a lot of people.&lt;/p&gt;

&lt;p&gt;That is closer to how product teams ship. You do not wait only for support tickets to learn that a flow is broken. You look at where users drop. With this process, documentation can work the same way.&lt;/p&gt;

&lt;p&gt;Mintlify’s report also talks about companies pointing agents at feedback and turning those signals into documentation patches. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anthropic’s public story in that report is a good example of the same direction: collect signals, triage them, and turn the real documentation gaps into PRs while people still decide what ships.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our version is built for our docs stack, but the idea is similar. Read the failure then draft a fix, review it and then merge it to publish.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not solve
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the limits when it comes to auto updating the documentation, because otherwise this starts sounding like this is so easy so we all need to follow this process.&lt;/p&gt;

&lt;p&gt;An agent job is only as good as the context you give it and the documentation truth underneath it. If the product behavior is unclear, the draft will be unclear too. Suppose, the question was actually a support issue or a configuration problem, opening a docs PR is the wrong way to update your docs. So, somebody still has to look at the PR and ask: &lt;strong&gt;is this a real documentation gap&lt;/strong&gt;, or are we documenting around a product problem?&lt;/p&gt;

&lt;p&gt;There is also noise. Automatic “not found” detection based on phrasing is useful, but it is not perfect. Sometimes the model hedges or the docs do contain the answer and &lt;strong&gt;RAG&lt;/strong&gt; based retrieval missed it. It could also be that the reader asked something outside the product which is not documented at all. So the loop has to stay reviewable and easy to change.&lt;/p&gt;

&lt;p&gt;And closing the loop on gaps is only one part of keeping docs updated. Product changes still need writers and engineers who notice what shipped. Version drift, naming consistency, and release notes still need their own updating systems. I will discuss this more in Part 3 of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in my day-to-day work
&lt;/h2&gt;

&lt;p&gt;Before this mechanism existed, a weak docs answer mostly meant I would find out later, if at all. Now a wrong answer can show up as a concrete artifact: a logged question, a Slack escalation, a feedback note, or a draft PR waiting for me to review. This changed how I prioritize my work. Instead of only asking “which page should I rewrite or update this week,” I can also ask “which unanswered questions keep repeating” and “which draft PRs are actually fixing the same gap.” It does not remove writing but it changes where the writing starts. &lt;/p&gt;

&lt;p&gt;Sometimes it starts from a blank outline or from a reader's question that already proved the gap exists. For me, that is the useful definition of docs systems work. You still own clarity and correctness and you also own the path from failure back into the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Documentation used to be something we published and hoped people say all is correct and we never needs to update it. But, in real world that is not the case. Now that most of the modern docs systems also uses an AI assistant to answer reader's questions, if those systems hit a gap and nothing happens afterward, the gap stays invisible until someone catches and flag it. In this post I only covered the loop itself: detect the failure, capture feedback, draft the docs gap, and keep a human on the merge. &lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt; I will write about the docs assistant side in more detail, including how we collect information without turning every chat into noise. In &lt;strong&gt;Part 3&lt;/strong&gt; I will write about preparing the documentation for agents in the first place, so fewer questions fail before the gap even starts. &lt;/p&gt;

&lt;p&gt;If you maintain developer docs and you already have an assistant on the site, I would start with one question: When the bot cannot answer, where does that failure go? If the answer is nowhere, that is the first system you need to build.&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>documentation</category>
      <category>ai</category>
      <category>bestofdev</category>
    </item>
    <item>
      <title>Linting Technical Docs for AI Readability Using Vale</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Thu, 17 Sep 2026 10:24:01 +0000</pubDate>
      <link>https://dev.to/meharshit/linting-technical-docs-for-ai-readability-using-vale-3b12</link>
      <guid>https://dev.to/meharshit/linting-technical-docs-for-ai-readability-using-vale-3b12</guid>
      <description>&lt;p&gt;When I started learning more seriously about documentation for AI systems, I noticed something I had probably been ignoring for a long time. A lot of documentation problems are not really writing problems but are &lt;strong&gt;consistency&lt;/strong&gt; problems.&lt;/p&gt;

&lt;p&gt;The API is &lt;em&gt;PlatformClient&lt;/em&gt; on one page and &lt;em&gt;platformClient&lt;/em&gt; in an example on another. If you write technical documentation, you can relate to this.&lt;/p&gt;

&lt;p&gt;In my docs, one feature is called &lt;strong&gt;Form Filling&lt;/strong&gt; in the navigation, &lt;strong&gt;form filling&lt;/strong&gt; in the body, and &lt;strong&gt;Form-Filling&lt;/strong&gt; somewhere else.&lt;/p&gt;

&lt;p&gt;Someone writes:&lt;/p&gt;

&lt;p&gt;You might need to create a session before calling the API.&lt;/p&gt;

&lt;p&gt;But the session is actually required. None of these things would make me reject a documentation page.&lt;/p&gt;

&lt;p&gt;You might even read the page, understand what it means, and move on. But when you maintain a large documentation set, these small differences start showing up everywhere, and now that the same documentation is also being used by AI assistants and RAG systems, you need to start paying more attention to them.&lt;/p&gt;

&lt;p&gt;Not because I think documentation should be written for AI. It should or shouldn’t, but if we can make the documentation clearer and more consistent for people, we also give machines cleaner information to work with.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;Vale&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The style guide problem
&lt;/h2&gt;

&lt;p&gt;Technical writers love style guides, and my docs also have one. We create them for everything like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Capitalization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Product names&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API terminology&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Headings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Voice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sentence length, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, it becomes a large document which is hard to maintain, and something a person has to remember.&lt;/p&gt;

&lt;p&gt;You can tell another writer:&lt;/p&gt;

&lt;p&gt;Always use &lt;strong&gt;PlatformClient&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Six months later, someone writes &lt;strong&gt;Platform Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can tell everyone not to use vague language in instructions.&lt;/p&gt;

&lt;p&gt;Another writes &lt;em&gt;you might need to...&lt;/em&gt;. This is normal when you have multiple writers, hundreds of pages, and documentation that keeps changing. Therefore, instead of putting a lot of rules in a document and hoping everyone remembers them, I started thinking about which rules could simply be checked automatically.&lt;/p&gt;

&lt;p&gt;That is where I used Vale. Vale is a prose linter. It can run against Markdown and MDX and lets you define your own writing rules. It is basically the same idea as linting code it just that you are linting documentation here.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I start using Vale
&lt;/h2&gt;

&lt;p&gt;Don’t start by creating 100 Vale rules. This is what most beginners do. Instead, the first rule is to protect information. Let’s understand this. Product names, API identifiers, important terminology- these are the things that should be written in a specific way.&lt;/p&gt;

&lt;p&gt;For example, imagine that the official feature name is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form Filling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don't want this appearing randomly across the documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Form filling
form filling
Form-Filling

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes lowercase form filling might be correct in body text, which is fine, but a good Vale rule should understand the context, not the rule itself.&lt;/p&gt;

&lt;p&gt;For example, I might want &lt;strong&gt;Form Filling&lt;/strong&gt; in a title but allow **form filling **in normal prose. Vale lets me create such rules instead of relying entirely on manual review.&lt;/p&gt;

&lt;p&gt;A simple rule could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;existence&lt;/span&gt;

&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'Form&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Filling'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;titles."&lt;/span&gt;
&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;

&lt;span class="na"&gt;nonword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\bForm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filling\b'&lt;/span&gt;

&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a very small rule. But that is the point. I don't need Vale to understand my entire documentation strategy; it just needs to catch the things I already know are wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vale language
&lt;/h2&gt;

&lt;p&gt;There is another category that I find interesting in vale&lt;/p&gt;

&lt;p&gt;Words like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maybe
might
could
often
seems
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These words are not automatically bad. Sometimes "might" is exactly the right word. But technical documentation has plenty of places where we use these words because we haven't been precise enough.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;You might need to create a session before sending audio.&lt;/p&gt;

&lt;p&gt;If the session is required, why say "might"?&lt;/p&gt;

&lt;p&gt;Just say:&lt;/p&gt;

&lt;p&gt;Create a session before sending audio.&lt;/p&gt;

&lt;p&gt;So I can use Vale to flag words like might or could:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;existence&lt;/span&gt;

&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Avoid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vague&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;language.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;specific."&lt;/span&gt;
&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;

&lt;span class="na"&gt;nonword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b(maybe|might|could|often|seems)\b'&lt;/span&gt;

&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I used warning. I don't want the build to fail every time someone writes "could”, instead, I want the writer to stop for a second and decide whether the sentence can be made more precise, and this distinction matters a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vale for API identifiers
&lt;/h2&gt;

&lt;p&gt;I am much stricter with API identifiers. If the actual SDK class is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PlatformClient&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;then I want the documentation to say exactly that.&lt;/p&gt;

&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platformClient
Platform Client
platform client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same goes for parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emr_encounter_id
session_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These aren't style preferences but are identifiers. If I get the capitalisation of a normal sentence wrong, nobody's application breaks, but if I get an API identifier wrong in a copy-paste example, there is a much bigger problem.&lt;/p&gt;

&lt;p&gt;This is also where AI-generated code makes consistency more important. If an AI system gets PlatformClient from one page and platformClient from another, I would much rather have caught that inconsistency in the source documentation than try to fix the generated answer later.&lt;/p&gt;

&lt;p&gt;Sentence length is another thing Vale can check. I like shorter sentences in technical documentation not because every sentence needs to be short, but because long sentences often contain several instructions that should have been separated.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Before calling the endpoint, make sure the session exists, that it is active, and that the session ID returned when you created it is the same ID you use in the request.&lt;/p&gt;

&lt;p&gt;There are several pieces of information here.&lt;/p&gt;

&lt;p&gt;I would probably write:&lt;/p&gt;

&lt;p&gt;Create a session before calling the endpoint. The session must be active. Use the session_id returned when you create the session.&lt;/p&gt;

&lt;p&gt;Much easier to scan, maintain, and reuse as individual pieces of information. I would still make sentence length a warning. I don't want a linter forcing writers to write unnatural prose just to make a number happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Vale actually fits
&lt;/h2&gt;

&lt;p&gt;For me, Vale makes the most sense when it becomes part of the normal documentation workflow. As a tech writer, I can get feedback in the editor itself. The same checks can run before committing, and then CI can run them again when the documentation goes through a pull request.&lt;/p&gt;

&lt;p&gt;A simple GitHub Actions job looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Vale&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;errata-ai/vale-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**/*.mdx'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you work with Vale, be careful about what blocks a PR. An incorrect API identifier might be an error, or a vague word should probably be a warning. A style suggestion might just be a suggestion. If everything becomes an error, you will eventually start treating Vale as noise, and this gets frustrating over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Vale fits in AI workflow
&lt;/h2&gt;

&lt;p&gt;I don't think Vale makes documentation "AI readable" in some special technical sense. What it does is help remove some of the mess from documentation, like consistent terminology, correct identifiers, etc.&lt;/p&gt;

&lt;p&gt;My workflow has changed a bit with Vale. Now I also think about how another system might consume the same page. Developers might understand that Form Filling and form filling refer to the same thing. A documentation search system may still have to deal with the difference. But an AI assistant may retrieve several pieces of documentation and use them as context. Using value makes your source material less ambiguous to deal with. That is the real reason why linting is important in the AI world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I have always thought of documentation quality as something that comes from many small decisions. Like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Writing the right sidebar for an API.&lt;br&gt;
The right example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A clear heading that is easy to search and makes sense as per that page's topics. Vale doesn't replace any of that work. It just lets you automate some of the decisions we keep making manually over time, and as your documentation grows and gets consumed by AI systems, I think that is worth doing.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>ai</category>
      <category>documentation</category>
      <category>startup</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:18:44 +0000</pubDate>
      <link>https://dev.to/meharshit/-3625</link>
      <guid>https://dev.to/meharshit/-3625</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g" class="crayons-story__hidden-navigation-link"&gt;From Technical Writer to DevEx Engineer: The AI Shift in Documentation&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/meharshit" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4120351%2F027fcca2-4052-4753-8cd3-8093c2606e76.png" alt="meharshit profile" class="crayons-avatar__image" width="800" height="966"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/meharshit" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Harshit Satyaseel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Harshit Satyaseel
                
                
              
              &lt;div id="story-author-preview-content-4672833" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/meharshit" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4120351%2F027fcca2-4052-4753-8cd3-8093c2606e76.png" class="crayons-avatar__image" alt="" width="800" height="966"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Harshit Satyaseel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 17&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g" id="article-link-4672833"&gt;
          From Technical Writer to DevEx Engineer: The AI Shift in Documentation
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/documentation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;documentation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devrel"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devrel&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>From Technical Writer to DevEx Engineer: The AI Shift in Documentation</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:12:02 +0000</pubDate>
      <link>https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g</link>
      <guid>https://dev.to/meharshit/from-technical-writer-to-devex-engineer-the-ai-shift-in-documentation-38g</guid>
      <description>&lt;p&gt;With AI coming in, the shift in technical writing is rapidly changing. I used to always introduce myself as a technical writer, but not anymore. If you look at most of the JDs for tech writers, they don't feel like tech writer JDs. It feels like a DevEx engineer.&lt;/p&gt;

&lt;p&gt;This shift is real because what I have been doing for the past couple of years, I totally relate to this, and I will explain why this is happening in this blog.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shift
&lt;/h2&gt;

&lt;p&gt;If I reflect on my work, most of my job does not look like “writing” anymore. Sometimes I am building style enforcement with Vale, putting quality gates around commits or designing docs for AI coding tools, debugging how assistants used our pages, and treating documentation like a developer product with release checks, failure modes, and ownership, and this is the shift I feel all the time.&lt;/p&gt;

&lt;p&gt;AI did not replace my writing work, but I feel it definitely changed what my job role used to be. When docs became something humans, bots, and agents all consume together, the writer’s responsibility moved beyond publishing doc pages to owning how a developer interacts and feel with your published content.&lt;/p&gt;

&lt;p&gt;Now, a tech writer needs to focus on developer experience and make sure they can find the right product, get the right answer, and use it without getting lost or misled. Writing is still part of technical writing, but it is not the whole role anymore. A big part of it has become DevEx engineering for documentation. Let's understand this more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “DevEx engineer” means for documentation people
&lt;/h2&gt;

&lt;p&gt;Let's be clear about what I mean when I say the role is &lt;em&gt;DevEx&lt;/em&gt;: I do not mean that it's a coding or backend engineering role.&lt;br&gt;
DevEx in documentation, for me, is the work of making developers successful with a product integration. &lt;/p&gt;

&lt;p&gt;For a long time, we used to think that only SDK engineers, API designers, or developer advocates had to take care of this, and we all treated documentation work as additional support material. This understanding does not hold anymore.&lt;/p&gt;

&lt;p&gt;If your docs are wrong, outdated, hard to navigate, or easy for an AI tool to misread, developers fail in the same way they fail with a bad SDK error or a confusing API. So, docs are not support material but are part of the product experience, not an attachment around it now.&lt;/p&gt;

&lt;p&gt;So when I say technical writers are moving into a DevEx role, I want to emphasise that: we still write, but now have to focus more on designing the system around the writing framework. We need to care about how developers discover, trust, and use what we publish.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With DevEx, we measure problems in developer outcomes, not only in “page is live.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a big shift. We now write scripts, configure systems, define&lt;br&gt;
rules for AI systems, and make sure that readers get what they need at the right time. This also brings down to one more challenge: we need to write and read code more than before. &lt;/p&gt;

&lt;p&gt;The core skill of a tech writer did not change; we still need to understand the product and make it usable for developers. It's just that the toolkit we used to have around our skills got bigger because of AI.&lt;/p&gt;
&lt;h2&gt;
  
  
  The change in Job descriptions says it all
&lt;/h2&gt;

&lt;p&gt;Open a few &lt;strong&gt;Technical Writer&lt;/strong&gt; or &lt;strong&gt;Documentation Engineer&lt;/strong&gt; JDs from the last few months, and you instantly notice sentences that used to live in other roles such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docs-as-code and CI familiarity&lt;/li&gt;
&lt;li&gt;OpenAPI / SDK documentation experience&lt;/li&gt;
&lt;li&gt;Information architecture for multi-product platforms&lt;/li&gt;
&lt;li&gt;AI search, assistants, or “AI-ready documentation”&lt;/li&gt;
&lt;li&gt;Style guides with automated linting and checks&lt;/li&gt;
&lt;li&gt;GitHub workflows for automation and CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these make the JD less about we need a good tech writer to someone who can write well, but also takes care of entire doc systems to create a clean developer experience for the end readers.&lt;/p&gt;

&lt;p&gt;Here is a simple way I think about old vs. new JD and the expectations it brings with this shift:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Older TW expectation&lt;/th&gt;
&lt;th&gt;What the work often includes now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write and update pages&lt;/td&gt;
&lt;td&gt;Write new or update pages and keep product language consistent across the whole site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow a style guide&lt;/td&gt;
&lt;td&gt;Enforce the style guide with tools like Vale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish after review&lt;/td&gt;
&lt;td&gt;Pass checks before merge: links, anchors, validation, version drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support human readers&lt;/td&gt;
&lt;td&gt;Support humans, docs assistants, and AI coding tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix docs after feedback tickets&lt;/td&gt;
&lt;td&gt;Catch failures earlier from bot chats, support teams, and broken journeys to fix automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Own content quality&lt;/td&gt;
&lt;td&gt;Own content quality plus docs tooling and release processes in an automated way&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your company has multiple products under one docs site, this expansion happens even faster, and this is something I can say from my personal experience, where I handle multiple products under one docs site. &lt;/p&gt;
&lt;h2&gt;
  
  
  How my week changed as a technical writer
&lt;/h2&gt;

&lt;p&gt;I still spend a lot of my time writing documentation, but it has drastically changed from what I used to do five years ago. Usually, my first draft is AI-driven now, followed by rigorous checks and reviews. Sometimes the docs are automatically generated from Docs Gap directly from the site, where I just review, make changes and publish.&lt;/p&gt;

&lt;p&gt;For new docs that are written, I usually do this now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Understand what shipped in the existing product
2. Update the docs pages led by AI and human review
3. Check whether naming and IA still hold for that new page
4. Run quality gates before merge
5. Think about how an AI tool or docs assistant will use this page to answer a human
6. Fix the failures that show up after publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the writing is normal too; for instance, when a new API comes for documentation, I talk to engineering, draft the guide using OpenAPI spec, edit examples, get it reviewed, and finally ship using the automated CI/CD process.&lt;/p&gt;

&lt;p&gt;But sometimes it may look totally different. I get warnings from my automated checks that Vale is flagging terminology drift or that a change log is out of sync with a package version. An AI coding workflow needs a clearer skill or index, or a docs assistant is answering from the wrong product section in the RAG pipeline. Those days do not look like “technical writing” but more like a DevEx engineer who is trying to fix these problems.&lt;/p&gt;

&lt;p&gt;This is a shift in role that not everyone is ready to adapt to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vale and style systems are not “nice to have” anymore
&lt;/h2&gt;

&lt;p&gt;Let’s talk about Vale, because this is one of the clearest examples of the DevEx work. As you all know, Vale is a prose linter. You define its rules, and it checks your docs the way a code linter checks code. If the rule fails, the change can be blocked or skipped based on what you want to do when a new doc is pushed.&lt;/p&gt;

&lt;p&gt;Earlier in my career, my manager used to say that style guides were mostly documents people were supposed to remember. Some teams followed them well, and some did not. Inconsistencies stayed in the site for months until someone flagged and tagged us via a ticket.&lt;br&gt;
With AI writing content, it became a bigger problem because inconsistent naming fails RAG pipelines that my docs bot uses to retrieve content.&lt;/p&gt;

&lt;p&gt;For example, if Product A’s auth concept is named one way on three pages and another way on two pages, a developer reading the docs may still guess, but a docs assistant or coding agent may pick the wrong meaning and give a wrong answer.&lt;/p&gt;

&lt;p&gt;So in my work, Vale stopped being “grammar help” and became part of daily checks that run on every commit.&lt;/p&gt;

&lt;p&gt;If you want to set it up, it will look like this in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protect exact product names and API/SDK names&lt;/li&gt;
&lt;li&gt;Stop writers and AI drafts from rewriting code identifiers&lt;/li&gt;
&lt;li&gt;Keep body writing rules separate from UI title rules&lt;/li&gt;
&lt;li&gt;Catch banned vague phrases that hide missing detail&lt;/li&gt;
&lt;li&gt;Make the style guide rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I call this DevEx thinking, which is applied to language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human style guide
      ↓
Vale rules in the repo
      ↓
Same language across pages
      ↓
Fewer mixed-up answers for people and AI tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not need to become a full-time engineer to do this. I treated documentation language like a contract and enforced it. This alone already pulls the role away from a technical writer who only writes the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality checks: treating docs like something you release
&lt;/h2&gt;

&lt;p&gt;The second big change that I do almost all the time is maintaining a release discipline. Software teams do not merge backend code just like that. They run checks; PR are approved, etc. Same way I do for docs now. For example, if your docs say Package X is on version 1.2, but the package already shipped 1.4, developers will get confused. &lt;/p&gt;

&lt;p&gt;If an AI tool reads that outdated page and generates integration code, the problem multiplies. If a heading change breaks deep links, both humans and assistants lose the citation path, and I have seen this happening with my docs.&lt;/p&gt;

&lt;p&gt;So docs work starts to include checks, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminology checks with Vale&lt;/li&gt;
&lt;li&gt;Broken link and anchor checks on commit and PR merges&lt;/li&gt;
&lt;li&gt;Docs site validation &lt;/li&gt;
&lt;li&gt;changelog/package version drift checks using custom GitHub flows&lt;/li&gt;
&lt;li&gt;A real review of product claims before publishing using PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can imagine the flow like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  A[Update docs] --&amp;gt; B[Vale / terminology]
  A --&amp;gt; C[Link and anchor checks]
  A --&amp;gt; D[Version / changelog drift]
  A --&amp;gt; E[Docs site validate]
  B --&amp;gt; F{Ready to merge?}
  C --&amp;gt; F
  D --&amp;gt; F
  E --&amp;gt; F
  F --&amp;gt;|No| G[Fix the docs system issue]
  F --&amp;gt;|Yes| H[Publish]
  H --&amp;gt; I[Humans + AI tools use it]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is one reason JDs sound like DevEx. They are asking for people who can keep documentation reliable under change, not only people who can write a clear paragraph. For me, the mindset shift was simple: Publishing a page is not the finish line. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Passing the checks that protect developers is part of the finish line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Designing documentation for AI coding tools like Cursor
&lt;/h2&gt;

&lt;p&gt;This is something that feels newest to me. Developers are not only browsing docs sites now. They are also asking coding assistants like Claude or Cursor to implement from your documentation. Some teams now publish skills, MCP access, or curated indexes so tools can find the right pages faster.&lt;/p&gt;

&lt;p&gt;This completely changes how you write and organize your pages now.&lt;/p&gt;

&lt;p&gt;It is really important that you start asking questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the task structure clear enough that a tool can follow create -&amp;gt; configure -&amp;gt; verify?&lt;/li&gt;
&lt;li&gt;Are Product A and Product B separated cleanly in navigation and page titles?&lt;/li&gt;
&lt;li&gt;Are auth concepts named exactly, every time?&lt;/li&gt;
&lt;li&gt;Do we give agents a trusted map of important pages, or do we make them guess from a giant site?&lt;/li&gt;
&lt;li&gt;Are examples complete enough that a model does not invent missing steps?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still documentation work. But it is documentation work with a second audience in mind: The AI Coding agents.&lt;/p&gt;

&lt;p&gt;A useful way to think about it is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One docs repo
   ├── Human reader: skims nav, examples, tutorials
   ├── Docs assistant: retrieves pages into chat answers
   └── Coding agent: follows skills / tools / structured guides
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only optimise for the human reading, the other two audiences feels left out and if you only optimize for machines, it is hard for human readers. So, how do we solve this? This is where thinking like DevEx engineer is keeping all three usable.&lt;/p&gt;

&lt;p&gt;I will be honest: this part of the work can feel strange at first. You are writing for readers who do not get tired, do not ask clarifying questions the same way, and will confidently continue from a doc page that is wrong. That is exactly why structure, naming, and completeness matter more than ever now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs assistants
&lt;/h2&gt;

&lt;p&gt;A lot of modern docs site have AI assistant built in to ask questions related to docs. My doc site also has one. Once your site has an assistant on top of the docs, the assistant becomes another way developers experience your documentation. If it mixes Product A with Product B, or cites the wrong guide, that failure is attached to your docs IA whether you personally trained the model or not.&lt;/p&gt;

&lt;p&gt;So tech writers needs to fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading chat failures&lt;/li&gt;
&lt;li&gt;Deciding whether the fix is copy, IA, naming, or assistant behavior&lt;/li&gt;
&lt;li&gt;Writing clearer product boundaries&lt;/li&gt;
&lt;li&gt;Testing important questions again after a fix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the moments where the old title feels outdated. You are not only the person who writes the page but now you are part of the team making sure developers get the right help from the docs platform.&lt;/p&gt;

&lt;p&gt;If you want the evaluation side of that work in more detail, I have written about &lt;a href="https://dev.to/meharshit/docs-as-evals-the-new-job-technical-writers-didnt-expect-269o"&gt;docs-as-evals &lt;/a&gt;separately. &lt;/p&gt;

&lt;p&gt;Here, the important career path is simpler: AI assistants made documentation failures visible in a new way. Visible failures create ownership and it pushes writers into DevEx-shaped work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills technical writers need to add in this shift
&lt;/h2&gt;

&lt;p&gt;If you are wondering what skills actually grow in this transition, this is the list I see from my own work and from those JDs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Product and systems thinking
&lt;/h3&gt;

&lt;p&gt;You need to stop thinking page by page and start think in journeys like onboard, authenticate, create a session, handle errors, upgrade versions, move between Product A and Product B.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docs-as-code habits
&lt;/h3&gt;

&lt;p&gt;Using Git, generating PR for reviews, branches, checks, and do not merge broken docs should be the part of your writing workflow. Not because you want to copy engineering processes but to keep things consistent. Docs break the same way code breaks when process is weak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tooling upgrades
&lt;/h3&gt;

&lt;p&gt;Vale rules, validation commands, small scripts, config files, maybe a dashboard for assistant feedback. You do not need to build everything, but you need to add these in the repo may be my doing some coding work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Information architecture under multi-product documentation
&lt;/h3&gt;

&lt;p&gt;Shared words are dangerous in a complex product docs. Good IA and naming consistency become developer experience work. So, as a tech writer you need to decide and figure out what needs to get done to achieve this.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI literacy for docs
&lt;/h3&gt;

&lt;p&gt;Do no only focus on better prompt writing but also learn how assistants retrieve, how coding tools consume pages, where they fail, and what content patterns reduce those failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaboration with engineering and product on outcomes
&lt;/h3&gt;

&lt;p&gt;You need to gather constant feedback on your docs work with engineering team to improve the process and consistency.&lt;/p&gt;

&lt;p&gt;None of the above replace writing skill but they are the add ons to a tech writer now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did not change
&lt;/h2&gt;

&lt;p&gt;Writing is not change. As a tech writer your core skill is still a strong structured writing. You need to write correctly both factually and conceptually. If the source docs are wrong, every assistant and coding tool will scale that wrongness. Honest limitations matter, talking to engineers are important and knowing when a sentence is hiding missing product truth is a key skill a tech writer needs to have.&lt;/p&gt;

&lt;p&gt;AI did not remove the need for technical writers who understand the product but added pressure on those writers to also own the systems that distribute and interpret the writing. So the identity shift is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;technical writer -&amp;gt; not a writer anymore&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;technical writer -&amp;gt; technical writer + docs DevEx owner&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  If you are a new technical writer reading this
&lt;/h2&gt;

&lt;p&gt;If you are new to this field, this is one of the best time to be a tech writer as it opens a lot of opportunities for you. You can develop these DevEX skills without rewiring your brain too much.&lt;/p&gt;

&lt;p&gt;Here is a starting path that I would recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Make your style guide enforceable.&lt;/strong&gt; Put 5 to 10 high-value Vale rules around product names and confusing terms.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add one quality gate that catches real pain.&lt;/strong&gt; Broken links or version drift are good first gates.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map your multi-product collisions.&lt;/strong&gt; List words that mean different things in Product A vs Product B. Fix naming and nav around those first.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch how AI tools use your docs.&lt;/strong&gt; Read assistant failures or ask a coding tool to implement from your guide, then fix what it gets wrong.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redefine “done” with your team.&lt;/strong&gt; Done should include the checks and follow-ups, not only the merged page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do this for a few months and your JD anxiety will make more sense. The industry is not randomly renaming roles as the the work changed, JD is catching up to that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Technical writing is still the foundation of my work. I still write, edit, structure, and explain but AI changed the scope around that foundation. Docs are now read by humans, answered through assistants, and consumed by coding tools. That means the person closest to the docs often ends up owning consistency systems, release checks, AI-facing structure, and the failures that show up after publish.&lt;/p&gt;

&lt;p&gt;That is why so many technical writer JDs feel like DevEx engineer JDs and this relate to the shift so strongly from my own last couple of years experience. If your title still says technical writer, that is fine. Keep learning and enjoy what you love.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>documentation</category>
      <category>devrel</category>
      <category>devops</category>
    </item>
    <item>
      <title>Docs-as-Evals: The New Job Technical Writers Didn’t Expect</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Mon, 14 Sep 2026 10:37:34 +0000</pubDate>
      <link>https://dev.to/meharshit/docs-as-evals-the-new-job-technical-writers-didnt-expect-269o</link>
      <guid>https://dev.to/meharshit/docs-as-evals-the-new-job-technical-writers-didnt-expect-269o</guid>
      <description>&lt;p&gt;When I started my career in technical writing, the requirements and expectations were simple: learn the product, talk to some SMEs or engineers, get the job done by completing the documentation work, and work on the collected feedback.&lt;/p&gt;

&lt;p&gt;But with AI coming in, it doesn’t feel the way it used to. The role is shifting dramatically, and so are expectations. But why?&lt;/p&gt;

&lt;p&gt;If you maintain a dashboard for your documentation insights, you must have noticed that AI agents now read your docs more than real humans do. This helps us understand the shift in the role. AI has changed the audience for our documentation. Real people still read docs, but agents read them more, mix them with chat history, and answer in your product’s voice. So “is this page clear?” is only half the review and feedback you get.&lt;/p&gt;

&lt;p&gt;Let’s understand this better.&lt;/p&gt;

&lt;p&gt;Last month, I noticed something in my bot’s chat history. The Docs AI answered all questions for a user’s query, but some of the links it shared back were wrong. The links did not belong to the context the user asked about. What I understood after my initial investigation was that the AI got stuck in the wrong documentation context, and the carried-over long chat history made it share the wrong link.&lt;/p&gt;

&lt;p&gt;If a Docs bot answers incorrectly from your documentation, someone has to own the answer it responded with. That someone is the technical writer now. This is where the role is expanding now, and I call it docs-as-evals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What docs-as-evals means for technical writers
&lt;/h2&gt;

&lt;p&gt;If I explain this in simple language, it is the process of treating documentation quality as something you can test by asking real user questions against your docs, then testing whether the AI answer is correct or not.&lt;/p&gt;

&lt;p&gt;With this process, we are trying to evaluate how the bot behaves in a certain way; for instance, we want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did it stay on the right product?&lt;/li&gt;
&lt;li&gt;Are citations correct?&lt;/li&gt;
&lt;li&gt;Did it keep conversation history as context when the user followed up&lt;/li&gt;
&lt;li&gt;Did it drop the history when the user switched products?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are important parameters to know, and technical writers are good at evaluating these things. We understand the mess and have data to check: which words users actually typed, which page is the real source of truth, etc. As tech writers, we always used to map these things in our heads and in the style guide. But now it needs to be used in evaluating AI answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with RAG in developer documentation
&lt;/h2&gt;

&lt;p&gt;Most of the product tech writers document are multi-product systems. The product that I document is also multi-product, and I have noticed that on a multi-product developer site like mine, RAG does not usually fail with a blank answer. It fails with an answer that reads correct but makes no sense.&lt;/p&gt;

&lt;p&gt;One of the core problems that my docs assistant had was that it used to send a large conversation window on every turn. Great for follow-ups. Bad for product switches in multi-product documentation.&lt;/p&gt;

&lt;p&gt;Let me make you understand with an example from my docs bot.&lt;/p&gt;

&lt;p&gt;In my docs bot chat history, I saw a user ask questions about Ambient APIs, followed by how to create a session, then check headers, and status. Everything was fine until they asked something like, “How do I quick-start with Mobile SDK?”&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%2Fno2uh8cjoinbldfrzvg4.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%2Fno2uh8cjoinbldfrzvg4.png" alt="Image process" width="800" height="1266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a human, that is a clear example of a new topic shift, but for the docs bot, it was still one long chat. So the old product messages stayed in the API request it sent, and the answer started mixing things up and producing wrong links.&lt;/p&gt;

&lt;p&gt;In one real test I ran, that product-switch question was still carrying about nine earlier turns. After we changed how context was handled, the same question only carried about two turns, and it cleared the old thread. Same question. Better result that I will share with you at the end of this blog.&lt;/p&gt;

&lt;p&gt;A few other problems around this that I noticed were:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Users do not ask with perfect product names. They say things like “the SDK” or “how do I authenticate?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's common in any product that different products share the same words, so a RAG-based chatbot usually gets confused. Handling this is a challenge because the current page, the chat history, and the new question can all pull the answer in different directions and scenarios.&lt;/p&gt;

&lt;p&gt;So the issue was not only “write clearer docs.” The issue was also: when should the bot remember the old chat, and when should it forget it?&lt;/p&gt;

&lt;p&gt;That is where docs-as-evals helped me.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I started testing this
&lt;/h2&gt;

&lt;p&gt;I made a simple list of real user questions from the bot history that I collected from my docs dashboard. For each question, I wrote what “correct” meant for me as a tech writer. My idea was to evaluate the following on the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which product should this answer belong to when the bot answers?&lt;/li&gt;
&lt;li&gt;Is the new question a follow-up or a new topic?&lt;/li&gt;
&lt;li&gt;Should I keep history, or should I drop it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of this as a math set ok.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const selectionSet = [
{
prompt: "How do I create a session with Product A?",
expectProduct: "product_a",
keepHistory: false,
},
{
prompt: "How do I quickstart with Product B?",
expectProduct: "product_b",
expectMode: "topic_shift",
keepHistory: false, // drop Product A chat
},
{
prompt: "Tell me more about the authentication step",
expectProduct: "product_b",
expectMode: "follow_up",
keepHistory: true, // stay on Product B
},
{
prompt: "How do I quickstart with Product C?",
expectProduct: "product_c",
expectMode: "topic_shift",
keepHistory: false,
},
];
And this list became the golden set for me, which I used later to solve the problem.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I checked in each answer
&lt;/h2&gt;

&lt;p&gt;I kept the checks simple and evaluated the answers based on the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the docs bot stay on the right product for a topic shift?
Were the right links shared?&lt;/li&gt;
&lt;li&gt;For follow-ups, was the history kept or deleted?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the user asked about another product and the bot linked other pages, I marked it as a fail. Even if the answer it gave was correct.&lt;/p&gt;

&lt;p&gt;In code, it looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function isWrongCite(expectedProduct, citedPaths) {
if (!expectedProduct || !citedPaths.length) return null;
const matching = citedPaths.filter((path) =&amp;gt;
path.includes(`/${expectedProduct}/`)
);
// Fail if no links match, or most links are from the wrong product
if (matching.length === 0) return true;
return matching.length &amp;lt; citedPaths.length / 2;
}
// Example
isWrongCite("product_b", [
"/product_a/sessions/create",
"/product_a/authentication",
]);
// → true (wrong product links)
When I compared before and after on those key turns, the difference was clear. Product switches started landing on the right docs. Follow-ups stayed on the same product. The bot stopped dragging the old conversation into every new question.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I compared before and after on those key turns, the difference was clear. Product switches started landing on the right docs. Follow-ups stayed on the same product. The bot stopped dragging the old conversation into every new question.&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%2F75otz2hj5svvdw9nhoo9.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%2F75otz2hj5svvdw9nhoo9.png" alt="result 1" width="799" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In those switch turns, the request size also dropped a lot in our runs, as you can see from the image below, from around 11k–17k characters to around 5k. This was because we stopped sending the wrong chat history with the questions.&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%2Fzoyklv9j1xv5y0y1mumn.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%2Fzoyklv9j1xv5y0y1mumn.png" alt="result 2" width="799" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the technical writer role
&lt;/h2&gt;

&lt;p&gt;Technical writers never used to evaluate RAG-based systems. But now we do, and this is why I say the role is expanding. This is understandable: RAG is new, and most doc sites never used it. We still do the old work: learn the product, talk to engineers, write the docs, handle feedback.&lt;/p&gt;

&lt;p&gt;But now, there is a new question we have to own and answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the docs bot answers from our documentation, is that answer actually right?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is docs-as-evals for RAG systems in documentation. In my day-to-day work, it looks like this:&lt;/p&gt;

&lt;p&gt;I write docs, collect real questions from bot chats and support, then decide what correct looks like for those questions. I test them and use the failed ones to decide what to fix next, and this is something totally new to me. It is just documentation work for a world where AI reads our docs too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;RAG chatbots made it easy to get answers from long documentation, but they also created a new problem for technical writers to solve. Earlier, we mostly focused on information architecture to improve how a reader finds the right page. With RAG systems, that is not enough. We also have to check whether the AI finds the right answer, and that answer must be correct. I personally love this role change because it makes us more valuable in an uncertain world.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>documentation</category>
      <category>devrel</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Mon, 14 Sep 2026 07:00:43 +0000</pubDate>
      <link>https://dev.to/meharshit/-2b42</link>
      <guid>https://dev.to/meharshit/-2b42</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36" class="crayons-story__hidden-navigation-link"&gt;How to Build a Handbag Storefront with Storyblok and Next.js&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/meharshit" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F4120351%2F027fcca2-4052-4753-8cd3-8093c2606e76.png" alt="meharshit profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/meharshit" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Harshit Satyaseel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Harshit Satyaseel
                
                
              
              &lt;div id="story-author-preview-content-4647828" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/meharshit" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F4120351%2F027fcca2-4052-4753-8cd3-8093c2606e76.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Harshit Satyaseel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 14&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36" id="article-link-4647828"&gt;
          How to Build a Handbag Storefront with Storyblok and Next.js
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/storyblokchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;storyblokchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How to Build a Handbag Storefront with Storyblok and Next.js</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Mon, 14 Sep 2026 06:56:01 +0000</pubDate>
      <link>https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36</link>
      <guid>https://dev.to/meharshit/how-to-build-a-handbag-storefront-with-storyblok-and-nextjs-3j36</guid>
      <description>&lt;p&gt;In this blog post, we'll build an imaginary &lt;strong&gt;Mira Carry&lt;/strong&gt;, a small luxury handbag storefront website. We will manage marketing content in Storyblok, and Next.js will render the shop, blog, and a demo checkout. The finished &lt;strong&gt;Mira Carry home page&lt;/strong&gt;, rendered from a Storyblok &lt;code&gt;home&lt;/code&gt; story.&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%2Fdbc8cb5h3via88k2k66j.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%2Fdbc8cb5h3via88k2k66j.png" alt="Home" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This tutorial assumes you are comfortable with &lt;strong&gt;React&lt;/strong&gt; and the &lt;strong&gt;Next.js&lt;/strong&gt; App Router. Basic knowledge of Storyblok is &lt;strong&gt;required&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This tutorial has been tested with the following package versions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;next&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16.1.6&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;react&lt;/code&gt; / &lt;code&gt;react-dom&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^19.2.4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@storyblok/react&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^5.4.22&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want to explore the finished project first, open the &lt;a href="https://storyblok-interview-showcase.vercel.app" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; or clone &lt;a href="https://github.com/meharshit/storyblok-interview-showcase" rel="noopener noreferrer"&gt;GitHub repo &lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What we'll build&lt;/li&gt;
&lt;li&gt;APIs we use&lt;/li&gt;
&lt;li&gt;How Storyblok works with Next.js&lt;/li&gt;
&lt;li&gt;Set up the project and render a story&lt;/li&gt;
&lt;li&gt;Fetch a single story on dynamic routes&lt;/li&gt;
&lt;li&gt;Build the product catalog with folder queries&lt;/li&gt;
&lt;li&gt;Build category pages and product detail&lt;/li&gt;
&lt;li&gt;Build the blog feed and resolve author relations&lt;/li&gt;
&lt;li&gt;Enable draft mode and Visual Editor preview&lt;/li&gt;
&lt;li&gt;Product images and the shop grid&lt;/li&gt;
&lt;li&gt;Cart and demo checkout&lt;/li&gt;
&lt;li&gt;Optional: seed content with the Management API&lt;/li&gt;
&lt;li&gt;How the architecture fits together&lt;/li&gt;
&lt;li&gt;Wrapping up&lt;/li&gt;
&lt;li&gt;Additional resources&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Storyblok lets you manage content separately from your code repo. In this tutorial, we'll use that to build a handbag website's content: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home page modules&lt;/li&gt;
&lt;li&gt;Catalogue across four categories, product detail pages,&lt;/li&gt;
&lt;li&gt;Blog with author relations, and draft preview for editors.&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%2F10ei75q64zymw23upowp.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%2F10ei75q64zymw23upowp.png" alt="Architecture" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Editors update content in Storyblok. Next.js fetches that content through the Content Delivery API and renders React components.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We'll start by setting up the Next.js project and rendering a story from Storyblok. Then we'll fetch stories on dynamic routes, list products with folder queries, resolve blog author relations, and add draft mode for the Visual Editor.&lt;/p&gt;

&lt;p&gt;By the end, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A home page built from nested bloks (&lt;code&gt;hero&lt;/code&gt;, &lt;code&gt;category_strip&lt;/code&gt;, &lt;code&gt;product_grid&lt;/code&gt;, and more)&lt;/li&gt;
&lt;li&gt;A shop catalogue of products under &lt;code&gt;bags/&lt;/code&gt;, &lt;code&gt;purses/&lt;/code&gt;, &lt;code&gt;wallets/&lt;/code&gt;, and &lt;code&gt;accessories/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Category landings and product detail routes&lt;/li&gt;
&lt;li&gt;A blog index and articles with resolved authors&lt;/li&gt;
&lt;li&gt;Draft Mode so editors can preview unpublished changes&lt;/li&gt;
&lt;li&gt;A client-side cart and demo checkout (Next.js only — not Storyblok APIs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  APIs used in this tutorial
&lt;/h2&gt;

&lt;p&gt;We'll talk to &lt;strong&gt;Storyblok&lt;/strong&gt; through &lt;code&gt;@storyblok/react&lt;/code&gt; and its &lt;code&gt;apiPlugin&lt;/code&gt;. That client uses the &lt;strong&gt;Content Delivery API (CDN) v2&lt;/strong&gt;. Optional seed scripts use the &lt;strong&gt;Management API v1&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Delivery API v2
&lt;/h3&gt;

&lt;p&gt;Base URL (EU): &lt;code&gt;https://api.storyblok.com/v2/cdn/…&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Auth: &lt;code&gt;STORYBLOK_DELIVERY_API_TOKEN&lt;/code&gt; (Preview or Public token)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What we use it for&lt;/th&gt;
&lt;th&gt;Parameters we pass&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/cdn/stories/{slug}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Home, about, product detail, category pages, articles&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;version&lt;/code&gt;, &lt;code&gt;resolve_relations&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/cdn/stories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;Blog index, shop catalog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;version&lt;/code&gt;, &lt;code&gt;starts_with&lt;/code&gt;, &lt;code&gt;sort_by&lt;/code&gt;, &lt;code&gt;is_startpage&lt;/code&gt;, &lt;code&gt;per_page&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Docs: &lt;a href="https://www.storyblok.com/docs/api/content-delivery/v2" rel="noopener noreferrer"&gt;Content Delivery API&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;GET /cdn/stories/{slug}&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Fetches one story by full slug (&lt;code&gt;home&lt;/code&gt;, &lt;code&gt;bags/linen-tote&lt;/code&gt;, &lt;code&gt;blog/priya-commute-tote&lt;/code&gt;, and so on):&lt;/p&gt;
&lt;h3&gt;
  
  
  Code example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storyblokApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`cdn/stories/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fullSlug&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 'draft' | 'published'&lt;/span&gt;
  &lt;span class="na"&gt;resolve_relations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article.author&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;You'll see this pattern in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/app/[[...slug]]/page.js&lt;/code&gt; — catch-all pages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/app/[category]/page.js&lt;/code&gt; — category landings (&lt;code&gt;shop/bags&lt;/code&gt;, and so on)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/app/about/page.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;GET /cdn/stories&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Fetches multiple stories under a folder prefix.&lt;/p&gt;

&lt;p&gt;Blog index (newest first):&lt;/p&gt;
&lt;h3&gt;
  
  
  Code example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storyblokApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cdn/stories&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;is_startpage&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="na"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first_published_at:desc&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;File: &lt;code&gt;src/app/blog/page.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Shop catalogue (one request per category folder):&lt;/p&gt;
&lt;h3&gt;
  
  
  Code example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cdn/stories&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bags/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// also purses/, wallets/, accessories/&lt;/span&gt;
  &lt;span class="na"&gt;is_startpage&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="na"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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;File: &lt;code&gt;src/lib/products.js&lt;/code&gt; (&lt;code&gt;fetchAllProducts()&lt;/code&gt;)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Values we use&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;draft&lt;/code&gt; or &lt;code&gt;published&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Draft Mode / Visual Editor vs production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;starts_with&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blog/&lt;/code&gt;, &lt;code&gt;bags/&lt;/code&gt;, …&lt;/td&gt;
&lt;td&gt;Limit results to a folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sort_by&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;first_published_at:desc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blog ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_startpage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skip folder index stories in lists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;per_page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Catalog page size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolve_relations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;article.author&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inline the author story on articles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After a fetch with relations, &lt;code&gt;applyResolvedRelations()&lt;/code&gt; in &lt;code&gt;src/lib/storyblok.js&lt;/code&gt; swaps author UUIDs for full story objects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Management API v1 (seed scripts only)
&lt;/h3&gt;

&lt;p&gt;Base URL: &lt;code&gt;https://mapi.storyblok.com/v1/spaces/{space_id}/…&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Auth: &lt;code&gt;STORYBLOK_MANAGEMENT_TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/seed-full-catalog.mjs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates product stories and category pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/fix-bag-images-and-stories.mjs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates blog copy and CMS asset URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scripts/upload-storyblok-images.mjs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Uploads images to Storyblok assets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Docs: &lt;a href="https://www.storyblok.com/docs/api/management" rel="noopener noreferrer"&gt;Management API&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What we don't use
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API / feature&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GraphQL API&lt;/td&gt;
&lt;td&gt;We use REST through &lt;code&gt;@storyblok/react&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commerce/checkout APIs&lt;/td&gt;
&lt;td&gt;Checkout is a demo form under &lt;code&gt;src/app/checkout/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web-hooks in app code&lt;/td&gt;
&lt;td&gt;ISR (&lt;code&gt;revalidate = 60&lt;/code&gt;) refreshes cached pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  How Storyblok works with Next.js
&lt;/h2&gt;

&lt;p&gt;Before diving in, let's first understand how all the pieces fit together. In Storyblok, content is made of &lt;strong&gt;blocks (bloks)&lt;/strong&gt; and &lt;strong&gt;stories&lt;/strong&gt;. Each story has a &lt;code&gt;content.component&lt;/code&gt; type, for example &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;product&lt;/code&gt;, or &lt;code&gt;article&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%2F0fm5m5icj0r2brivs3sc.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%2F0fm5m5icj0r2brivs3sc.png" alt="Content tree" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Demo_2 space: content types, nestable blocks, and how the &lt;code&gt;home&lt;/code&gt; story nests bloks in &lt;code&gt;body&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In our Next.js app, each blok maps to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A React component (such as &lt;code&gt;Hero.jsx&lt;/code&gt; or &lt;code&gt;ProductGrid.jsx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An entry in the &lt;code&gt;storyblokInit({ components: { … } })&lt;/code&gt; registry in &lt;code&gt;src/lib/storyblok.js&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At request time, a Server Component calls &lt;code&gt;storyblokApi.get(…)&lt;/code&gt; on the Content Delivery API. Storyblok returns JSON for the story and its nested bloks. &lt;code&gt;&amp;lt;StoryblokStory story={story} /&amp;gt;&lt;/code&gt; walks that tree and renders the matching React component for each blok.&lt;/p&gt;

&lt;p&gt;A typical home story looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page (home)
  └── body[]
        ├── hero
        ├── category_strip
        ├── product_grid
        ├── testimonial
        └── story_strip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set up the project and render a story
&lt;/h2&gt;

&lt;p&gt;Install the project and render a story from Storyblok so we know that the setup works.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clone and install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/meharshit/storyblok-interview-showcase.git
&lt;span class="nb"&gt;cd &lt;/span&gt;storyblok-interview-showcase
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add your delivery token
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;.env.local&lt;/code&gt;:&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="nv"&gt;STORYBLOK_DELIVERY_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_preview_token
&lt;span class="nv"&gt;STORYBLOK_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu
&lt;span class="nv"&gt;STORYBLOK_API_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.storyblok.com
&lt;span class="nv"&gt;DRAFT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev-draft-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the Preview token under Storyblok --&amp;gt; space &lt;strong&gt;Demo_2&lt;/strong&gt; --&amp;gt; Settings --&amp;gt; Access tokens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: If the Blueprint wizard blocks &lt;strong&gt;Settings&lt;/strong&gt;, skip the wizard and open the space settings URL directly, then copy the Preview token.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Initialise the SDK
&lt;/h3&gt;

&lt;p&gt;In our repo, the file &lt;code&gt;src/lib/storyblok.js&lt;/code&gt; registers blok components and configures the CDN client:&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;apiPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storyblokInit&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;@storyblok/react/rsc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getStoryblokApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;storyblokInit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STORYBLOK_DELIVERY_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;apiPlugin&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;product_grid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProductGrid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;category_strip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CategoryStrip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// …&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;apiOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STORYBLOK_REGION&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;apiPlugin&lt;/code&gt; is what gives you &lt;code&gt;storyblokApi.get('cdn/stories/…')&lt;/code&gt; against Content Delivery API v2.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Run the dev server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:3000/&lt;/code&gt;. The catch-all route loads the &lt;code&gt;home&lt;/code&gt; story and renders it — the same layout you saw in the screenshot at the top of this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetch a single story on dynamic routes
&lt;/h2&gt;

&lt;p&gt;File in the repo: &lt;code&gt;src/app/[[...slug]]/page.js&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URL path&lt;/th&gt;
&lt;th&gt;Story slug sent to the CDN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;home&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/about&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;about&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/bags/linen-tote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bags/linen-tote&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/blog/priya-commute-tote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blog/priya-commute-tote&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core fetch looks like this:&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;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getStoryVersion&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;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storyblokApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`cdn/stories/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fullSlug&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resolve_relations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ARTICLE_RELATIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 'article.author'&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;story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyResolvedRelations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rels&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;StoryblokStory&lt;/span&gt; &lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&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;&lt;code&gt;getStoryVersion()&lt;/code&gt; returns &lt;code&gt;draft&lt;/code&gt; when Draft Mode is on (and during local development), and &lt;code&gt;published&lt;/code&gt; in production unless Draft Mode is enabled.&lt;/p&gt;

&lt;p&gt;Caching uses &lt;code&gt;export const revalidate = 60&lt;/code&gt;, so pages can refresh about every minute without a full redeploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the product catalog with folder queries
&lt;/h2&gt;

&lt;p&gt;The shop page (&lt;code&gt;/shop&lt;/code&gt;) lists product stories from four folder prefixes. It calls &lt;code&gt;GET /cdn/stories&lt;/code&gt; once per category, there's no custom catalog endpoint.&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%2Fiuryq3pfkwjqp39m5iuu.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%2Fiuryq3pfkwjqp39m5iuu.png" alt="page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Shop all: search, category filters, and product cards sourced from Storyblok folder queries.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;File: &lt;code&gt;src/lib/products.js&lt;/code&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;const&lt;/span&gt; &lt;span class="nx"&gt;prefixes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bags/&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;purses/&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;wallets/&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;accessories/&lt;/span&gt;&lt;span class="dl"&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;prefix&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;prefixes&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;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cdn/stories&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;is_startpage&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="na"&gt;per_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// keep stories where content.component === 'product'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each product story has fields such as &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;category&lt;/code&gt;. The shop UI turns them into cards with search and filters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build category pages and product detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Category landing pages
&lt;/h3&gt;

&lt;p&gt;Routes: &lt;code&gt;/bags&lt;/code&gt;, &lt;code&gt;/purses&lt;/code&gt;, &lt;code&gt;/wallets&lt;/code&gt;, &lt;code&gt;/accessories&lt;/code&gt;&lt;br&gt;&lt;br&gt;
File: &lt;code&gt;src/app/[category]/page.js&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%2Fofdjfy9eig4zb66dq8mc.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%2Fofdjfy9eig4zb66dq8mc.png" alt="Bags category landing" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Category landings are Storyblok &lt;code&gt;page&lt;/code&gt; stories (for example &lt;code&gt;shop/bags&lt;/code&gt;) with their own bloks.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Story slug&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/bags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shop/bags&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/purses&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shop/purses&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/wallets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shop/wallets&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/accessories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shop/accessories&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;storyblokApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`cdn/stories/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;story&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="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those stories are &lt;code&gt;page&lt;/code&gt; types. Their &lt;code&gt;body&lt;/code&gt; can include &lt;code&gt;product_grid&lt;/code&gt; and other bloks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Product detail
&lt;/h3&gt;

&lt;p&gt;Product URLs use the catch-all route. For example, &lt;code&gt;/purses/chain-shoulder&lt;/code&gt; becomes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET /cdn/stories/purses/chain-shoulder&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%2F6lery1cqu6fftx5iy4u6.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%2F6lery1cqu6fftx5iy4u6.png" alt="Product detail page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Product detail: title, price, and description come from the Storyblok &lt;code&gt;product&lt;/code&gt; story.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The story’s &lt;code&gt;content.component&lt;/code&gt; is &lt;code&gt;product&lt;/code&gt;. &lt;code&gt;Product.jsx&lt;/code&gt; renders it and handles add-to-cart.&lt;/p&gt;
&lt;h2&gt;
  
  
  Build the blog feed and resolve author relations
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Blog index
&lt;/h3&gt;

&lt;p&gt;File: &lt;code&gt;src/app/blog/page.js&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%2Fnykltkt909yy5q1oiysc.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%2Fnykltkt909yy5q1oiysc.png" alt="Customer stories blog index" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The blog index lists &lt;code&gt;article&lt;/code&gt; stories under &lt;code&gt;blog/&lt;/code&gt;, sorted by publish date.&lt;/em&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;await&lt;/span&gt; &lt;span class="nx"&gt;storyblokApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cdn/stories&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="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;starts_with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blog/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;is_startpage&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="na"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first_published_at:desc&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;Keep stories where &lt;code&gt;content.component === 'article'&lt;/code&gt;, then link each card to &lt;code&gt;/{full_slug}&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Article and author
&lt;/h3&gt;

&lt;p&gt;Articles store &lt;code&gt;author&lt;/code&gt; as a Stories field that points at &lt;code&gt;authors/{slug}&lt;/code&gt;. When you fetch a single article, pass:&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;resolve_relations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article.author&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storyblok returns related authors in &lt;code&gt;data.rels&lt;/code&gt;. &lt;code&gt;applyResolvedRelations()&lt;/code&gt; replaces the UUID with the full author object so &lt;code&gt;Article.jsx&lt;/code&gt; can show name, bio, and avatar.&lt;/p&gt;

&lt;p&gt;That relation string lives as &lt;code&gt;ARTICLE_RELATIONS = 'article.author'&lt;/code&gt; in &lt;code&gt;src/lib/storyblok.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable draft mode and Visual Editor preview
&lt;/h2&gt;

&lt;p&gt;The Visual Editor needs the frontend to request &lt;code&gt;version=draft&lt;/code&gt;. We'll use Next.js &lt;strong&gt;Draft Mode&lt;/strong&gt; and a small API route for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Draft entry route
&lt;/h3&gt;

&lt;p&gt;File: &lt;code&gt;src/app/api/draft/route.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/draft?slug=home&amp;amp;secret=YOUR_DRAFT_SECRET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route validates an optional &lt;code&gt;DRAFT_SECRET&lt;/code&gt;, calls &lt;code&gt;draftMode().enable()&lt;/code&gt;, and redirects to the story path. To leave draft mode, hit &lt;code&gt;GET /api/disable-draft&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Editor URL
&lt;/h3&gt;

&lt;p&gt;In Storyblok space settings, set the preview URL to your deployed site, or to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-site.vercel.app/api/draft?slug=home&amp;amp;secret=YOUR_DRAFT_SECRET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an editor opens a story in the Visual Editor, Storyblok loads that preview URL so they can click components bridged with &lt;code&gt;storyblokEditable&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product images and the shop grid
&lt;/h2&gt;

&lt;p&gt;For this demo, each story slug maps to a verified JPG in &lt;code&gt;public/products/&lt;/code&gt; through `src/lib/images.js. It still has Storyblok asset fields for later use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;js&lt;br&gt;
export function resolveImageSrc(_image, { slug } = {}) {&lt;br&gt;
  return imagePathForSlug(slug || '');&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;js&lt;br&gt;
'purses/clara-clutch': '/products/burgundy-gray-crescent.jpg',&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The CMS still owns copy and structure; the frontend keeps product photography consistent. To change an image, add a file under &lt;code&gt;public/products/&lt;/code&gt; and update the slug map.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cart and demo checkout
&lt;/h2&gt;

&lt;p&gt;Cart state lives in React context (&lt;code&gt;src/components/cart/CartProvider.jsx&lt;/code&gt;). It isn't stored in Storyblok.&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%2F8narbwl7nweqc06j37ke.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%2F8narbwl7nweqc06j37ke.png" alt="Demo checkout" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Checkout page is a just UI, we have integrated any real payment method. This tutorial is focused on the Content Delivery API.&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;Feature&lt;/th&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add to bag&lt;/td&gt;
&lt;td&gt;Client state keyed by product slug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cart drawer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CartDrawer.jsx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkout&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/checkout&lt;/code&gt; — demo card form, no payment API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Optional: seed content with the Management API
&lt;/h2&gt;

&lt;p&gt;If space Demo_2 is empty, you can seed it with a Management token:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;export STORYBLOK_MANAGEMENT_TOKEN=sb_pat_...&lt;br&gt;
export STORYBLOK_SPACE_ID=&lt;br&gt;
node scripts/seed-full-catalog.mjs&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The script creates stories with &lt;code&gt;POST&lt;/code&gt; / &lt;code&gt;PATCH&lt;/code&gt; on &lt;code&gt;https://mapi.storyblok.com/v1/spaces/{id}/stories&lt;/code&gt;. Product photos on the site still come from &lt;code&gt;public/products/&lt;/code&gt; unless you also run &lt;code&gt;upload-storyblok-images.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caution:&lt;br&gt;
Never put the Management API token in Vercel or commit it to Git. Use it locally for seeding only.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How the architecture fits together
&lt;/h2&gt;

&lt;p&gt;By now you've built each piece of Mira Carry. So, let's understand them together.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem we solve in our demo
&lt;/h3&gt;

&lt;p&gt;Marketing people can change the hero, featured products, and blog posts without asking a developer for a deploy every time. &lt;/p&gt;

&lt;h3&gt;
  
  
  The approach
&lt;/h3&gt;

&lt;p&gt;We have used a &lt;strong&gt;headless CMS&lt;/strong&gt; (Storyblok) for content and &lt;strong&gt;Next.js&lt;/strong&gt; for the storefront. Marketing people work in Storyblok UI. The site reads JSON from the Content Delivery API and renders React components. Content and presentation stay separate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Content is managed in Storyblok, delivered over the CDN API, and rendered by Next.js.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The content model
&lt;/h3&gt;

&lt;p&gt;The Storyblok space is structured so editors can compose pages without touching code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;How it's modeled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Home, about, category landings&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;page&lt;/code&gt; stories with a &lt;code&gt;body&lt;/code&gt; field of nestable bloks (&lt;code&gt;hero&lt;/code&gt;, &lt;code&gt;category_strip&lt;/code&gt;, &lt;code&gt;product_grid&lt;/code&gt;, …)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Products&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;product&lt;/code&gt; stories in folders (&lt;code&gt;bags/&lt;/code&gt;, &lt;code&gt;purses/&lt;/code&gt;, &lt;code&gt;wallets/&lt;/code&gt;, &lt;code&gt;accessories/&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;article&lt;/code&gt; stories under &lt;code&gt;blog/&lt;/code&gt;, each linked to an &lt;code&gt;author&lt;/code&gt; story&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That model is what makes the Visual Editor useful: editors nest and reorder bloks; the frontend already knows how to render each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  How we fetch content
&lt;/h3&gt;

&lt;p&gt;At runtime the app only talks to the &lt;strong&gt;Content Delivery API v2&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;API call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One page, product, or article&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /cdn/stories/{slug}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shop catalog or blog index&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /cdn/stories&lt;/code&gt; with &lt;code&gt;starts_with&lt;/code&gt; (for example &lt;code&gt;bags/&lt;/code&gt; or &lt;code&gt;blog/&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Auth uses &lt;code&gt;STORYBLOK_DELIVERY_API_TOKEN&lt;/code&gt;. We pass &lt;code&gt;version&lt;/code&gt; (&lt;code&gt;draft&lt;/code&gt; or &lt;code&gt;published&lt;/code&gt;) and, for articles, &lt;code&gt;resolve_relations: 'article.author'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Management API&lt;/strong&gt; is optional and local-only — seed scripts create stories; the live site never calls it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How we render content
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;src/lib/storyblok.js&lt;/code&gt;, &lt;code&gt;storyblokInit&lt;/code&gt; registers every blok technical name to a React component:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;react&lt;br&gt;
components: {&lt;br&gt;
  hero: Hero,&lt;br&gt;
  product_grid: ProductGrid,&lt;br&gt;
  product: Product,&lt;br&gt;
  article: Article,&lt;br&gt;
  //&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When Next.js fetches a story, &lt;code&gt;&amp;lt;StoryblokStory /&amp;gt;&lt;/code&gt; walks the JSON tree. A blok with &lt;code&gt;component: "hero"&lt;/code&gt; becomes &lt;code&gt;&amp;lt;Hero /&amp;gt;&lt;/code&gt; with that blok's fields as props. If the Storyblok name and the registry key don't match, that block won't render.&lt;/p&gt;

&lt;h3&gt;
  
  
  How preview works
&lt;/h3&gt;

&lt;p&gt;Editors need to see unpublished changes; visitors must only see published content.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/api/draft&lt;/code&gt; enables Next.js Draft Mode (optionally protected by &lt;code&gt;DRAFT_SECRET&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getStoryVersion()&lt;/code&gt; returns &lt;code&gt;draft&lt;/code&gt; or &lt;code&gt;published&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every Storyblok fetch uses that &lt;code&gt;version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Visual Editor preview URL can point at the draft endpoint so clicks in Storyblok load unpublished content.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;You've now got a headless storefront that uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content Delivery API v2 for runtime reads&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@storyblok/react&lt;/code&gt; to map bloks to components&lt;/li&gt;
&lt;li&gt;Relations for blog authors&lt;/li&gt;
&lt;li&gt;Draft Mode for editor preview&lt;/li&gt;
&lt;li&gt;ISR for production caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Search, cart, and checkout stay in Next.js. They sit alongside the CMS without needing extra Storyblok APIs.&lt;/p&gt;

</description>
      <category>storyblokchallenge</category>
      <category>react</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Technical Writers Can Make Your AI Prompts Better</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Mon, 14 Sep 2026 05:52:59 +0000</pubDate>
      <link>https://dev.to/meharshit/technical-writers-can-make-your-ai-prompts-better-3dc4</link>
      <guid>https://dev.to/meharshit/technical-writers-can-make-your-ai-prompts-better-3dc4</guid>
      <description>&lt;p&gt;A few months ago, my engineering lead Slacked me a file: a system prompt for our new AI-powered note generator. He wanted me to look at that text file. I opened the file; the prompt felt like it was a lazy mess, bloated, ambiguous, and full of filler.&lt;/p&gt;

&lt;p&gt;If they had used that prompt in production, every one of those extra words would end up on the API bill.&lt;br&gt;
 So, I took that prompt and spent about a week refactoring it using the same style guide, information architecture, and defensive writing rules that I use for my company's developer documentation. That was when I realised something:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prompt engineering is not engineering at all. It is technical writing&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Sloppy AI writing
&lt;/h2&gt;

&lt;p&gt;In software documentation, sloppy AI writing is mostly ineffective because it wastes tokens. When developers build LLM-powered features using prompts, they often treat the model like a human colleague.&lt;/p&gt;

&lt;p&gt;I have seen devs writing prompts the same way they would write a message to a coworker on Slack. But that makes less sense for an LLM. An LLM does not read a prompt the way a developer reads a Slack message. It relies on the instructions and patterns in the text, so unclear and unnecessary wording can make the output less clear.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Could you please be so kind as to analyse the following text very carefully,"
In the above prompt, you have just spent 15 tokens saying very little. I call this a lazy prompt. A lazy prompt is like a half recipe or, say, an easy recipe which unfortunately does not result in tasty food.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why lazy prompts can crash your backend database
&lt;/h2&gt;

&lt;p&gt;One of the biggest headaches our engineering team faced was parser failures. We needed the LLM to output raw JSON so our backend could ingest the data. Instead, the model kept returning conversational preambles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"Sure! Here is the structured JSON clinical note you requested based on the transcript:"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"symptoms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cough"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fever"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Because&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wrapped&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;markdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;blocks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...),&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;our backend parser threw a 500 error and crashed. The engineers’ first reaction was to write regular expressions and retry loops to strip out the markdown blocks and conversational text. But that resulted in another problem.&lt;/p&gt;

&lt;p&gt;If the parser failed, the system automatically retried the API call, doubling the cost and latency for that transaction. Our production audits showed that 34% of our API calls were retries triggered by formatting failures. As a technical writer, my approach was to write defensively. What do I mean when I say this? Let’s understand with an example.&lt;/p&gt;

&lt;p&gt;As technical writers, we don’t just tell users how to install a plugin. We also explain what to do when the installation fails. This is a &lt;strong&gt;defensive technique of writing&lt;/strong&gt;. I applied the same thinking to that prompt and added explicit negative scenarios and error-handling patterns.&lt;/p&gt;

&lt;p&gt;I removed conversational padding: &lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“Do not include introductory or concluding remarks. Do not say ‘Sure, here is the JSON.’ Return only the raw JSON object.”
Banned markdown blocks: For example: “Do not wrap the output in markdown code blocks (such as ```

json). Start the response directly with the opening curly brace {.
Defined empty states like: “If no medications are discussed, return an empty array []. Do not omit the key, and do not return null."


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By writing defensively, we dropped our parser failure rate to near zero. We did not write a single line of regex or retry logic but just wrote better instructions, and that is what a technical writer does all day.&lt;/p&gt;

&lt;h2&gt;
  
  
  How large language models read and why layout is everything
&lt;/h2&gt;

&lt;p&gt;In Technical writing, structure matters a lot. Writing in a structured manner gives readers a better mental model. For example, good structured writing will have a high-level overview at the top, step-by-step guides in the middle, and reference tables at the bottom. LLMs require the same kind of structured information.&lt;/p&gt;

&lt;p&gt;Research from “Landmark research from Stanford and UC Berkeley (Liu et al., Lost in the Middle)” showed that an LLM’s retrieval accuracy drops significantly when key instructions or data sit in the middle of long, unstructured contexts.&lt;/p&gt;

&lt;p&gt;The model pays the most attention to the beginning and the end of the prompt; the middle part is skimmed and scanned. To counter this middle-part attention degradation, as a prompt writer, it is important to structure your prompts like software specifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Role / System Persona (Head)&lt;/strong&gt;: Establish the context and behavioural boundaries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Goal / Objective&lt;/strong&gt;: Define the exact task to be performed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints (Negative &amp;amp; Positive)&lt;/strong&gt;: Set the guardrails, including what to do and what not to do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context / Input Data&lt;/strong&gt;: Put the raw content to be processed inside clear XML tags, such as &lt;code&gt;&amp;lt;transcript&amp;gt;...&amp;lt;/transcript&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Output Schema (Tail)&lt;/strong&gt;: Put the precise response format at the end, where the model’s attention is closest.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is not to make the prompt look neat, but to put the important instructions where they are less likely to get lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “lazy” prompt vs. the “tech-written” spec: Comparison
&lt;/h2&gt;

&lt;p&gt;To see these principles in action, let us look at a real-world scenario from my documentation. Imagine we are building an AI feature that parses unstructured clinical transcripts into a structured JSON format for an electronic health record (EHR) system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lazy prompt example:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
plaintext
You are a helpful medical assistant. I am going to give you a transcript of a doctor talking to a patient, and I want you to extract the patient's symptoms and any medications they discussed.
Please put them in a JSON format so my system can read it. Make sure you are accurate and don't make things up. If there are no medications, just leave it blank.
Here is the transcript:
[Transcript content]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Flaws in the above prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ambiguous Schema&lt;/strong&gt;: “JSON format” is highly non-deterministic. The model may return {"symptoms": [...], "medications": [...]} in one run, and {"patient_symptoms": ..., "drugs": ...} in the next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No Negative Scenarios&lt;/strong&gt;: The model will almost certainly include conversational fillers like “Sure, here is the structured JSON for the transcript you provided:”, which will crash any standard JSON parser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No Error Handling&lt;/strong&gt;: “Leave it blank” could mean returning an empty string, an empty array, null, or omitting the key entirely.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How I would write this as a tech writer:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
markdown
# Role
You are a clinical data parser. Your sole task is to extract symptoms and medications from clinical transcripts.

# Constraints
Output must be a single, valid JSON object matching the schema below.

- Do not include any conversational text, markdown formatting blocks (such as

 ```json), preambles, or postscripts.

- Extract only symptoms and medications explicitly stated in the transcript. Do not infer or extrapolate.

# Output Schema
{
"symptoms": ["string"], // List of symptoms explicitly mentioned. If none, return an empty array [].
"medications": [
{
  "name": "string", // Generic or brand name of the drug.

  "dosage": "string" // Dosage specified (e.g., "50mg"). If not specified, return "N/A".

}
] // List of medications. If none, return an empty array [].
}

# Input
Transcript: [Transcript content]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s evaluate this prompt now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear example&lt;/strong&gt;: The explicit JSON schema and negative scenario guarantee that the output is immediately understandable by the AI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Structure&lt;/strong&gt;: The model has zero formatting decisions to make, dropping latency and eliminating reasoning cost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Defensive writing&lt;/strong&gt;: Clear instructions for empty states ([], "N/A") prevent the model from guessing or omitting keys.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;AI has led to a quiet panic in the technical writing community, and many of the top-level executives think, why do we even need tech writers at all. We need to understand that the core skill of a technical writer is not just writing words; it is analysing complex software APIs, SDKs, and cloud systems, identifying edge cases, and constructing structured, unambiguous instructions.&lt;/p&gt;

&lt;p&gt;As we move forward in the AI world, the demand for structured context architecture is going to skyrocket. Companies cannot build reliable AI features on top of sloppy AI content and conversational prompts. They need someone who treats language like code, and technical writers are the ones who understand information architecture best.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>documentation</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>AI Agents Don't Read Your API Docs Like Developers Do</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Sat, 12 Sep 2026 06:20:59 +0000</pubDate>
      <link>https://dev.to/meharshit/ai-agents-dont-read-your-api-docs-like-developers-do-3n8j</link>
      <guid>https://dev.to/meharshit/ai-agents-dont-read-your-api-docs-like-developers-do-3n8j</guid>
      <description>&lt;p&gt;I have spent a lot of my time writing API documentation, and I used to think that I understood what makes an OpenAPI specification good until AI came into the picture.&lt;/p&gt;

&lt;p&gt;Good API docs are pretty straightforward: you define paths correctly, write the right types for parameters, and explicitly mark required fields, etc.&lt;/p&gt;

&lt;p&gt;These are still important. But it starts to feel incomplete when a large share of your docs traffic comes from AI agents; good suddenly needs to mean something more. Look at my Mintlify dashboard. More than 50% of the traffic to my documentation site now comes from AI agents, and that changes the way I write the documentation.&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%2F7ua85wv7mic63x2c64s2.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%2F7ua85wv7mic63x2c64s2.png" alt="Dashboard" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you give an API to an AI agent, you will notice different kinds of problems that did not exist before.&lt;/p&gt;

&lt;p&gt;It needs to decide things like which operation matches a user's request, figure out what arguments are needed, understand relationships between operations, and sometimes recover when the API rejects its request.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Another problem is that an AI cannot build a mental model the way human developers do. Most AI has a tool definition and a context window. That difference changes how I think about OpenAPI now.&lt;/p&gt;

&lt;p&gt;I'm not saying that an OpenAPI document magically becomes an LLM's system prompt. It doesn't. Depending on the stack, the specification may be transformed into function definitions, JSON Schema, MCP tools, or another representation before the model sees it.&lt;br&gt;
The important part is what happens in between. The information in your API contract can become part of the information the model uses to decide what to do.&lt;/p&gt;

&lt;p&gt;Once you look at it that way, a few things that seemed like minor documentation details start looking more like API design decisions.&lt;/p&gt;

&lt;p&gt;The endpoint can be technically correct and still be bad for&amp;nbsp;agents. Consider this example as a normal endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create encounter&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Creates an encounter.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing wrong with this OpenAPI. A developer who already knows the product might understand exactly what it means. But imagine an agent has access to several operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

POST /encounters
GET  /encounters/&lt;span class="o"&gt;{&lt;/span&gt;encounter_id&lt;span class="o"&gt;}&lt;/span&gt;
GET  /encounters/&lt;span class="o"&gt;{&lt;/span&gt;encounter_id&lt;span class="o"&gt;}&lt;/span&gt;/sessions
GET  /encounters/&lt;span class="o"&gt;{&lt;/span&gt;encounter_id&lt;span class="o"&gt;}&lt;/span&gt;/artifacts
POST /encounters/&lt;span class="o"&gt;{&lt;/span&gt;encounter_id&lt;span class="o"&gt;}&lt;/span&gt;/complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the user says: "Start a new visit for this patient."&lt;/p&gt;

&lt;p&gt;The agent has to work hard to figure out that &lt;code&gt;POST /encounters&lt;/code&gt; is the right operation.&lt;/p&gt;

&lt;p&gt;The summary &lt;code&gt;"Create encounter"&lt;/code&gt; gives it very little help. It doesn't tell the LLM that this is the operation it needs to use when starting a new visit. It also doesn't say what an encounter represents in this particular API. A more useful description would be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start a new patient visit&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;Creates a new encounter for a patient visit.&lt;/span&gt;
    &lt;span class="s"&gt;Use this endpoint when starting a new visit. The returned&lt;/span&gt;
    &lt;span class="s"&gt;encounter_id identifies the visit and is required by the&lt;/span&gt;
    &lt;span class="s"&gt;endpoints used to retrieve the visit session and complete&lt;/span&gt;
    &lt;span class="s"&gt;the encounter. Do not use this endpoint to retrieve an existing encounter.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above API description is not prompt engineering but better API documentation written for LLMs. The difference is that it contains information about &lt;strong&gt;intent&lt;/strong&gt; and &lt;strong&gt;boundaries&lt;/strong&gt;, not just implementation.&lt;/p&gt;

&lt;p&gt;That distinction becomes important when an LLM has to read and choose between several operations that make the same sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  APIs have always had workflows, but we didn't always put them in the&amp;nbsp;contract
&lt;/h2&gt;

&lt;p&gt;What I mean when I say this, well, let's figure it out. Look at a simplified clinical workflow as an example from my documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Create encounter
       ↓
Start session
       ↓
Send audio
       ↓
Complete session
       ↓
Generate note

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer working through the integration will usually discover the steps pretty quickly. The quick start probably shows it. The API reference explains the individual endpoints. There may be a tutorial that ties everything together. But AI does not necessarily get that same experience.&lt;/p&gt;

&lt;p&gt;It may see five separate tools. Now suppose the model tries to send audio before a session has been created. The API returns an error. AI has to figure out what happened and what operation should come next.&lt;/p&gt;

&lt;p&gt;As humans, we can make this easier by putting the relationship where it matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Uploads audio for an existing visit session.&lt;/span&gt;
  &lt;span class="s"&gt;The session must already exist and must be active.&lt;/span&gt;
  &lt;span class="s"&gt;Use the session_id returned when creating the session.&lt;/span&gt;
  &lt;span class="s"&gt;Do not call this endpoint before a session has been created&lt;/span&gt;
  &lt;span class="s"&gt;or after the session has been completed.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traditional API documentation often focuses on the happy path, like here's what this endpoint does. &lt;/p&gt;

&lt;p&gt;Agent-facing documentation has another job: here's when this operation is valid, and here's when it isn't.&lt;/p&gt;

&lt;p&gt;These are not the same thing.&lt;/p&gt;

&lt;p&gt;The string problem is bigger than it&amp;nbsp;looks. There is another pattern I see frequently in OpenAPI specifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;span class="s"&gt;This is technically valid, but it can throw away information that the API already knows.&lt;/span&gt;
&lt;span class="na"&gt;If the only valid values are&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;active&lt;/span&gt;
&lt;span class="s"&gt;completed&lt;/span&gt;
&lt;span class="s"&gt;cancelled&lt;/span&gt;
&lt;span class="na"&gt;then the specification should say that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;completed&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cancelled&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is obvious for request validation, SDK generation, and documentation. It also matters when an agent is constructing the request. If the schema says string, the model has to determine what string belongs there. It may have seen the allowed values elsewhere, but there is no reason to make it guess when the API already has a finite set of valid values.&lt;/p&gt;

&lt;p&gt;The same applies to dates, identifiers, numeric ranges, and other constrained values.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If something is a date, use a date format.&lt;/li&gt;
&lt;li&gt;If there is a known pattern, express the pattern.&lt;/li&gt;
&lt;li&gt;If certain values are accepted, use an enum.&lt;/li&gt;
&lt;li&gt;Required fields must be marked as required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds almost too basic to be worth writing about, but there is an important principle behind it:&lt;/p&gt;

&lt;p&gt;Every rule that exists only in prose is another rule the model may have to infer. That does not mean every possible business rule should be added to JSON Schema. Some rules are too dynamic or too complex for that. But when a constraint can be represented accurately in the schema, there is little benefit in leaving it implicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Descriptions should answer the question the model is actually trying to&amp;nbsp;solve
&lt;/h2&gt;

&lt;p&gt;One of the biggest changes I would make to API descriptions is to stop thinking of summary and description as places where we simply repeat the endpoint name.&lt;/p&gt;

&lt;p&gt;For a human reader, this might be enough to start navigating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get patient&lt;/span&gt;
&lt;span class="s"&gt;For a model choosing between tools, it is much less useful.&lt;/span&gt;
&lt;span class="na"&gt;Imagine an API has&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;GET /patients/{id}&lt;/span&gt;
&lt;span class="s"&gt;GET /patients/{id}/encounters&lt;/span&gt;
&lt;span class="s"&gt;GET /patients/{id}/notes&lt;/span&gt;
&lt;span class="s"&gt;GET /patients/{id}/medications&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A user asks: &lt;strong&gt;"What happened during the patient's last visit?"&lt;/strong&gt; Which one should the model call?&lt;/p&gt;

&lt;p&gt;The answer is probably not obvious from the endpoint names alone. The API reference might have hundreds of pages explaining the system, but the model's immediate problem is much smaller:&lt;/p&gt;

&lt;p&gt;Which tool is relevant to this intent?&lt;/p&gt;

&lt;p&gt;So write descriptions that explain not just what an operation returns, but &lt;strong&gt;what kind of request should lead&lt;/strong&gt; to that operation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get encounters for a patient&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Returns the clinical encounters associated with a patient.&lt;/span&gt;
  &lt;span class="s"&gt;Use this endpoint when you need to identify a patient's visits&lt;/span&gt;
  &lt;span class="s"&gt;or determine which encounter to use for a follow-up operation.&lt;/span&gt;
  &lt;span class="s"&gt;Use GET /patients/{id}/notes instead when the user is asking&lt;/span&gt;
  &lt;span class="s"&gt;specifically for clinical notes.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells the reader not only what the endpoint does, but how it differs from a nearby capability. That is extremely useful when a tool set contains many operations with overlapping concepts.&lt;/p&gt;

&lt;p&gt;There is a limit, though: don't turn OpenAPI into a&amp;nbsp;prompt&amp;nbsp;&lt;br&gt;
Once you realise descriptions influence tool selection, there is a natural tendency to write big descriptions.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;You are an expert healthcare API agent. Carefully analyse the&lt;/span&gt;
  &lt;span class="s"&gt;user's request before using this tool. Think step by step about&lt;/span&gt;
  &lt;span class="s"&gt;whether this tool is appropriate. If the user is asking about...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, the API contract is carrying instructions that really belong in the agent layer. This is where tools like &lt;a href="https://www.mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt; are handy. It features a &lt;strong&gt;' For agents ' tag&lt;/strong&gt;, allowing technical writers to include agent-specific instructions that remain hidden from human readers.&lt;/p&gt;

&lt;p&gt;Also, maintaining such descriptions is a headache. As soon as the API changes, the description becomes stale, and suddenly your "prompt" is giving the model instructions that are no longer true.&lt;/p&gt;

&lt;p&gt;I prefer a simpler rule:&lt;/p&gt;

&lt;p&gt;Put &lt;strong&gt;facts&lt;/strong&gt; and &lt;strong&gt;constraints&lt;/strong&gt; in the API contract, whereas put general reasoning behaviour in the agent.&lt;/p&gt;

&lt;p&gt;The API description should tell the truth about the operation. It should explain when it applies, what it requires, what it returns, and what can make it fail. The agent should decide how to reason over that information. That separation makes the system much easier to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your error response is part of the conversation now
&lt;/h2&gt;

&lt;p&gt;This is probably the part of agent-oriented API design that I find most interesting. For a traditional integration, an error response is often treated as the end of a failed request. For an agent, it can become the input to the next decision. Suppose the agent calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
POST /sessions/123/audio
and receives:
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"error"&lt;/span&gt;: &lt;span class="s2"&gt;"Invalid request"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is not much context for the agent to act on that. It might think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should it retry or change the session ID&lt;/li&gt;
&lt;li&gt;Ask the user&lt;/li&gt;
&lt;li&gt;Check the session&lt;/li&gt;
&lt;li&gt;Call another endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine the API returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SESSION_NOT_ACTIVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audio can only be uploaded while the session is active."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"current_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That response gives the agent useful state. It knows the request failed because of the session state and understands which field is involved. Most importantly, it understands that simply retrying the same request is not going to help and that is why I think &lt;strong&gt;error design&lt;/strong&gt; deserves more attention when APIs are exposed to LLMs.&lt;/p&gt;

&lt;p&gt;A vague error like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bad request"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;frustrating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;developer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;agent.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structured&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;such&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SESSION_NOT_ACTIVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audio can only be uploaded while the session is active."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session_id"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives the agent context to reason about. While it doesn't guarantee the model won't make mistakes, it provides actionable information instead of forcing it to guess. &lt;/p&gt;

&lt;h2&gt;
  
  
  The other problem nobody talks about: too many&amp;nbsp;tools
&lt;/h2&gt;

&lt;p&gt;There is one more lesson that becomes obvious when you write a lot APIs. Making every endpoint available to an agent is not necessarily a good idea. Imagine a mature API with 300 operations. A human developer might appreciate having all of them documented. They can search the reference, jump between resources, and use the API according to their needs.&lt;/p&gt;

&lt;p&gt;An agent does not necessarily benefit from seeing all 300 operations at once.&lt;/p&gt;

&lt;p&gt;Suppose the user asks: "Get the note for this encounter."&lt;/p&gt;

&lt;p&gt;If the model has five tools related to notes, three related to encounters, and several generic document operations, tool selection becomes harder before the actual API call even happens.&lt;/p&gt;

&lt;p&gt;So, we need to separate two ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your API surface is not necessarily your agent tool surface.&lt;/li&gt;
&lt;li&gt;Your full OpenAPI specification can remain the source of truth for the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the set of operations you expose to an agent can be deliberately selected.&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%2Fmumb2qokw3cis3e0li49.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%2Fmumb2qokw3cis3e0li49.png" alt="Example" width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is also where OpenAPI and MCP start to overlap in interesting ways. Modern documentation and API tooling can selectively expose operations as tools instead of treating the entire API as one giant agent interface. Mintlify, for example, supports selecting OpenAPI operations for MCP exposure. The underlying idea is broader than any one documentation platform&lt;/p&gt;

&lt;h2&gt;
  
  
  Good agent design is partly about deciding what not to expose
&lt;/h2&gt;

&lt;p&gt;What changed for me when writing API documentation? The reason I find this topic interesting is that none of these ideas is really new.&lt;br&gt;
Strong API documentation has always needed clear descriptions, accurate schemas, good examples, sensible errors, and understandable workflows. What changed is the audience reading it.&lt;/p&gt;

&lt;p&gt;When I used to write API documentation primarily for developers, I could assume that the reader would connect information across the documentation. A developer can read an endpoint reference, notice an unfamiliar field, search for it, open the related guide, and come back.&lt;/p&gt;

&lt;p&gt;When an agent is choosing a tool, the cost of missing context is different. The model may never &lt;strong&gt;discover&lt;/strong&gt; the missing page. It may simply choose the wrong operation. And that changes what I consider a good description now.&lt;/p&gt;

&lt;p&gt;I no longer want an endpoint description to answer only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does this endpoint do?&lt;/li&gt;
&lt;li&gt;I also want it to answer:&lt;/li&gt;
&lt;li&gt;When should I use it?&lt;/li&gt;
&lt;li&gt;What needs to be true before I use it?&lt;/li&gt;
&lt;li&gt;What should I use instead when this is not the right operation?&lt;/li&gt;
&lt;li&gt;What information do I get back that I will need later?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A checklist you should keep in&amp;nbsp;mind&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If I were reviewing an OpenAPI specification and knew it would be consumed by an agent, I would take a different approach. Here is a checklist you can follow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can I tell why this operation exists from its description? If the answer is just &lt;code&gt;creates X&lt;/code&gt; or &lt;code&gt;gets X&lt;/code&gt;, there may not be enough context for tool selection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can I distinguish this endpoint from similar endpoints? If several operations deal with the same resource, say what makes each one different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are the constraints represented in the schema? Don't leave an enum, date format, numeric range, or required field as tribal knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are workflow dependencies visible? If one operation requires a resource or state created by another operation, document that relationship.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can an error tell the caller what went wrong? A status code is useful, but a structured error can provide the information needed for recovery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the agent really need every operation? A complete API is useful for developers. A focused tool set is often better for agents.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;OpenAPI did not change, but its audience&amp;nbsp;did. I don't think we need  to change a lot in how we used to write OpenAPI docs. But when it comes to AI, the approach needs a little tweak. Developers still benefit from tutorials and cross-page context, but agents rely on the immediate, machine-readable facts in your documentation. &lt;/p&gt;

&lt;p&gt;You should aim to make intent, constraints, workflows, and errors clear. Try this, and your API will be easier for both humans and LLMs to use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>documentation</category>
      <category>api</category>
    </item>
    <item>
      <title>I Stopped Sending the Whole Conversation to My RAG System</title>
      <dc:creator>Harshit Satyaseel</dc:creator>
      <pubDate>Fri, 11 Sep 2026 06:46:40 +0000</pubDate>
      <link>https://dev.to/meharshit/i-stopped-sending-the-whole-conversation-to-my-rag-system-nkk</link>
      <guid>https://dev.to/meharshit/i-stopped-sending-the-whole-conversation-to-my-rag-system-nkk</guid>
      <description>&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%2F4ljylv8879u8irqlmw4p.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%2F4ljylv8879u8irqlmw4p.png" alt="RAG in AI documentation" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re building technical docs assistants using RAG pipelines, stop dumping the entire chat transcript into every prompt.&lt;/p&gt;

&lt;p&gt;If you are currently appending full history, your pipeline is suffering from context bleed, where the LLM gets confused and mixes up parameters across completely different API endpoints. Plus, you are paying to resend 10,000 historical tokens to answer a 20-word follow-up.&lt;/p&gt;

&lt;p&gt;Here is what I did to fix this in my RAG pipeline: I built a Scope-Adaptive Context Gate (SADCG).&lt;/p&gt;

&lt;p&gt;Instead of stuffing the prompt, it classifies user intent and filters conversation history by topic scope before RAG retrieval even runs.&lt;/p&gt;

&lt;p&gt;What happened after implementing this: &lt;br&gt;
• 21% reduction in total per-chat credit cost &lt;br&gt;
• Up to 40% lower peak context size &lt;br&gt;
• Zero cross-topic hallucination in generated code and API answers&lt;/p&gt;

&lt;p&gt;If you are building with RAG pipelines and struggling with context bloat, I wrote a full breakdown of the scope gate architecture, 5 classification modes, and implementation lessons.&lt;/p&gt;

&lt;p&gt;Read the full story on&lt;a href="https://medium.com/@hsatyaseel/i-stopped-sending-the-whole-conversation-to-my-rag-system-785474642d6d" rel="noopener noreferrer"&gt; Medium&lt;/a&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>technical</category>
    </item>
  </channel>
</rss>
