<?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: TheKitBase</title>
    <description>The latest articles on DEV Community by TheKitBase (@thekitbase).</description>
    <link>https://dev.to/thekitbase</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%2F3953758%2F8055ca87-ed42-4f3d-8088-c7ef6426cba6.png</url>
      <title>DEV Community: TheKitBase</title>
      <link>https://dev.to/thekitbase</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thekitbase"/>
    <language>en</language>
    <item>
      <title>I Open-Sourced the Engine Behind One of My Paid Next.js Templates</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:58:50 +0000</pubDate>
      <link>https://dev.to/thekitbase/i-open-sourced-the-engine-behind-one-of-my-paid-nextjs-templates-mda</link>
      <guid>https://dev.to/thekitbase/i-open-sourced-the-engine-behind-one-of-my-paid-nextjs-templates-mda</guid>
      <description>&lt;p&gt;I sell Next.js templates for a living, which made this a slightly odd decision: I just gave away a real, complete piece of one of them for free.&lt;/p&gt;

&lt;p&gt;DocsKit is a documentation-site template I sell for $39. A few weeks ago I pulled out everything that makes it a genuinely usable docs site on its own - MDX rendering with real components, dark and light mode, a sidebar and table-of-contents layout - and published it as DocsKit Free, MIT licensed, on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why give part of it away
&lt;/h2&gt;

&lt;p&gt;The honest answer: because it split cleanly. A docs site has a natural line in it that a lot of other template types don't. The &lt;em&gt;engine&lt;/em&gt; - rendering MDX, navigating between pages, looking right in dark mode - is infrastructure. The &lt;em&gt;polish&lt;/em&gt; - syntax-highlighted code blocks, full-text search, a marketing homepage to sell the thing - is what people are actually paying for.&lt;/p&gt;

&lt;p&gt;So the free version isn't a crippled trial with a paywall bolted on. It's the real engine. You can clone it, deploy it, and have a working documentation site in about five minutes. What you don't get is Shiki syntax highlighting, Pagefind search, the marketing homepage, and the deeper config/API reference sections - those stayed in the paid version, and the README says so directly rather than making you find out by hitting a wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MDX content with &lt;code&gt;Callout&lt;/code&gt;, &lt;code&gt;CodeBlock&lt;/code&gt;, &lt;code&gt;Steps&lt;/code&gt;, and &lt;code&gt;Tabs&lt;/code&gt; components&lt;/li&gt;
&lt;li&gt;Sidebar navigation and an auto-generated table of contents&lt;/li&gt;
&lt;li&gt;Dark and light mode, no flash on load&lt;/li&gt;
&lt;li&gt;Static export - deploy to Vercel, Netlify, Cloudflare Pages, or any static host&lt;/li&gt;
&lt;li&gt;TypeScript strict, no YAML/JSON config file to fight with&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One thing I didn't expect to end up caring about
&lt;/h2&gt;

&lt;p&gt;While I was auditing accessibility across my whole paid catalog, I ran the same check against this free one. It's now WCAG 2.2 AA audited in both themes, at desktop and phone widths, with the check itself shipped in the repo (&lt;code&gt;npm run test:a11y&lt;/code&gt;) so it doesn't just pass once and silently rot the next time someone edits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://docskit-free.vercel.app/" rel="noopener noreferrer"&gt;https://docskit-free.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo (MIT licensed): &lt;a href="https://github.com/thekitbase/docskit-free" rel="noopener noreferrer"&gt;https://github.com/thekitbase/docskit-free&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click deploy to Vercel from the README&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use it for something, I'd genuinely like to see it - reply here or find me on X.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>opensource</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Accessible Next.js Templates: We Audited All 13 to WCAG 2.2 AA (and Ship the Check)</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Mon, 26 Oct 2026 09:15:21 +0000</pubDate>
      <link>https://dev.to/thekitbase/accessible-nextjs-templates-we-audited-all-13-to-wcag-22-aa-and-ship-the-check-46g9</link>
      <guid>https://dev.to/thekitbase/accessible-nextjs-templates-we-audited-all-13-to-wcag-22-aa-and-ship-the-check-46g9</guid>
      <description>&lt;p&gt;Our homepage used to say every template was "accessible by default". When we went back to check that claim properly, it was mostly untrue. Two templates, Chit and Relay, had real accessibility work behind them. For the other eleven we had good intentions and no evidence, and when we ran a proper check, 11 of the 13 templates passed one check or none. So we audited all 13, fixed everything the audit found, and made the check part of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the audit checks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;axe-core 4.13 with every WCAG 2.0, 2.1 and 2.2 A and AA rule, plus axe's best-practice rules&lt;/li&gt;
&lt;li&gt;Every page type in the template, in each theme it ships (light and dark for 12 of the 13), at a 1280px desktop width and a 390px phone width&lt;/li&gt;
&lt;li&gt;Menus, drawers, panels, tabs and dialogs checked open, not just the page as it first loads&lt;/li&gt;
&lt;li&gt;Text contrast measured from the rendered pixels wherever axe gives up, which is most text over photos, gradients and gradient text&lt;/li&gt;
&lt;li&gt;A keyboard pass that tabs through the page and fails any focus stop with no visible focus indicator, or focus landing on something hidden&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One check is one route or open state, in one theme, at one screen width. Across the 13 templates that comes to 526 checks, and all of them pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it found
&lt;/h2&gt;

&lt;p&gt;Almost nothing was exotic. The same few problems came up template after template, which is probably the most useful thing to know if you're auditing your own project:&lt;/p&gt;

&lt;h3&gt;
  
  
  One colour doing two jobs
&lt;/h3&gt;

&lt;p&gt;A brand accent that looks great as a button fill under white text is often too light to use as small text on the page background, and vice versa. Most contrast failures were this. Tweaking one token only moves the failure somewhere else, so the fix was to split it: one token for fills, one for text. Agency Studio's red, Chit's flame red and Marginalia's terracotta all work this way now, and each template's &lt;code&gt;CLAUDE.md&lt;/code&gt; says which token is which.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closed menus you could still tab into
&lt;/h3&gt;

&lt;p&gt;A slide-out drawer that is moved off-screen when closed is still in the page, so its links stay in the tab order. A keyboard user presses Tab and focus disappears into a menu they can't see. This turned up in mobile menus, a docs sidebar, a cart drawer and two dashboard detail panels. The fix: closed panels are inert, focus moves into a panel when it opens, Escape closes it, and focus goes back to the button that opened it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text over photos
&lt;/h3&gt;

&lt;p&gt;axe can't work out the contrast of text sitting on a photo or a gradient, so it marks it for review instead of failing it. Measuring the rendered pixels found real failures: index numbers over Stockwell's product photos, category labels over Fashion Storefront's images, and white text on Keel's gold gradient. These got darker scrims, small solid chips behind the text, or dark text instead of white.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structure and motion
&lt;/h3&gt;

&lt;p&gt;Missing h1s, skipped heading levels, pages with no main landmark, icon buttons with no name, and scroll-reveal sections that stayed invisible for anyone using reduced motion. Every template now respects the reduced-motion setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug our first pass missed
&lt;/h2&gt;

&lt;p&gt;After the first round of fixes, every template passed. Then, while fixing lint findings in SaaS Dashboard Pro, we spotted something in a screenshot that the audit hadn't: in the light theme, the sidebar's section labels (Overview, Manage, Account) were invisible. The class on them pointed at a colour name that doesn't exist, so the labels inherited the page's dark text colour and sat on a sidebar of exactly the same colour.&lt;/p&gt;

&lt;p&gt;Our check missed it for two reasons. axe treats text that is exactly the colour of its background as possibly hidden on purpose, so it doesn't fail it. And our pixel measurement looks for the rendered letters, but there were none to find. Now the check fails any text that matches its background. The same round of review found that SaaS Dashboard Pro's settings tabs and dialogs had never been opened by the check at all. Adding them took it from 40 checks to 62, and immediately caught a delete button that was unreadable in dark mode. A check only covers what it opens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every template, checked
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;Checks passed&lt;/th&gt;
&lt;th&gt;What the check covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agency Studio&lt;/td&gt;
&lt;td&gt;22/22&lt;/td&gt;
&lt;td&gt;5 page types, light and dark, phone and desktop, plus the open mobile menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI SaaS Landing&lt;/td&gt;
&lt;td&gt;40/40&lt;/td&gt;
&lt;td&gt;10 page types including docs, blog and auth, light and dark, phone and desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Tools Directory&lt;/td&gt;
&lt;td&gt;58/58&lt;/td&gt;
&lt;td&gt;13 page types, light and dark, phone and desktop, plus the mobile menu and search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chit&lt;/td&gt;
&lt;td&gt;40/40&lt;/td&gt;
&lt;td&gt;9 page types, dark and light, phone and desktop, plus the open ordering assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creative Portfolio&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;td&gt;4 page types, light and dark, phone and desktop, plus the open mobile menu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocsKit&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;td&gt;7 page types, light and dark, phone and desktop, plus the open docs drawer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elite CRM&lt;/td&gt;
&lt;td&gt;46/46&lt;/td&gt;
&lt;td&gt;9 page types, dark and light, phone and desktop, plus the mobile menu and the deal and contact panels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fashion Storefront&lt;/td&gt;
&lt;td&gt;18/18&lt;/td&gt;
&lt;td&gt;6 page types, phone and desktop (light-only template), plus the cart drawer, a missing-size error and a filled bag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keel SaaS Starter Kit&lt;/td&gt;
&lt;td&gt;64/64&lt;/td&gt;
&lt;td&gt;13 page types signed in and out, dark and light, phone and desktop, plus menus, command menu and assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marginalia&lt;/td&gt;
&lt;td&gt;52/52&lt;/td&gt;
&lt;td&gt;11 page types, light and dark, phone and desktop, plus both menus and an open citation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relay&lt;/td&gt;
&lt;td&gt;36/36&lt;/td&gt;
&lt;td&gt;6 page types signed in and out, dark and light, phone and desktop, plus menus, model picker and citations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS Dashboard Pro&lt;/td&gt;
&lt;td&gt;62/62&lt;/td&gt;
&lt;td&gt;9 page types, light and dark, phone and desktop, plus every settings tab, the mobile menu, the user detail panel, the invite panel and the delete dialog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stockwell&lt;/td&gt;
&lt;td&gt;40/40&lt;/td&gt;
&lt;td&gt;10 page types, light and dark, phone and desktop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why the check ships with the template
&lt;/h2&gt;

&lt;p&gt;An audit is a snapshot. The day you change the brand colour, or your coding agent adds a settings page, the snapshot is out of date. So the audit isn't a report we ran once and kept: it's the same script every template ships as &lt;code&gt;npm run test:a11y&lt;/code&gt;, and the routes and open states it checks live in a plain config file next to &lt;code&gt;package.json&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="c1"&gt;// a11y.config.mjs (SaaS Dashboard Pro, trimmed)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&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;/dashboard&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;/dashboard/users&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;/dashboard/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;themes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&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;dark&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;states&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete account dialog open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard/settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;steps&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;click&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button:has-text("Security")&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;click&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button:has-text("Delete Account")&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it after a build and it prints one line per check, then exits non-zero if anything fails, so it works in CI as is:&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;$ &lt;/span&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run &lt;span class="nb"&gt;test&lt;/span&gt;:a11y

pass / &lt;span class="o"&gt;(&lt;/span&gt;desktop light&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;15 focus stops&lt;span class="o"&gt;)&lt;/span&gt;
pass /about &lt;span class="o"&gt;(&lt;/span&gt;desktop light&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;27 focus stops&lt;span class="o"&gt;)&lt;/span&gt;
pass /services &lt;span class="o"&gt;(&lt;/span&gt;desktop light&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;8 focus stops&lt;span class="o"&gt;)&lt;/span&gt;
...
pass / &lt;span class="o"&gt;[&lt;/span&gt;mobile menu open] &lt;span class="o"&gt;(&lt;/span&gt;mobile dark&lt;span class="o"&gt;)&lt;/span&gt;

22/22 checks passed &lt;span class="o"&gt;(&lt;/span&gt;axe-core 4.13.0, wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa, best-practice&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each template's &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;.cursorrules&lt;/code&gt; now has an Accessibility section too: run the check before finishing, which token to use for text and which for fills, and how closed panels must behave. When you add a route or a dialog, you add it to the config, and the check covers it from then on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't cover
&lt;/h2&gt;

&lt;p&gt;Automated checks catch a lot, but not everything. Whether alt text actually describes the image, whether the reading order makes sense, and how a screen reader announces a custom widget still need a person. These templates have not been through a full manual screen reader audit, and we don't claim they have. What we do claim is narrower and checkable: every page type, in every theme, at both widths, with its menus and dialogs open, passes WCAG 2.2 AA automated checks, and you can run the same check yourself.&lt;/p&gt;

&lt;p&gt;Every template's results, coverage and fixes are on one page: &lt;a href="https://thekitbase.app/accessibility" rel="noopener noreferrer"&gt;see every template's accessibility results&lt;/a&gt;, or &lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;browse the full catalog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>a11y</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>The 6 Best Next.js AI Chat UI Kits in 2026 (Compared)</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:38:02 +0000</pubDate>
      <link>https://dev.to/thekitbase/the-6-best-nextjs-ai-chat-ui-kits-in-2026-compared-1m3n</link>
      <guid>https://dev.to/thekitbase/the-6-best-nextjs-ai-chat-ui-kits-in-2026-compared-1m3n</guid>
      <description>&lt;p&gt;Every AI product now needs a chat surface, and the chat surface is where users decide whether the product feels finished. Smooth token streaming, sources you can actually check, code that opens somewhere better than the middle of a transcript, a stop button that works mid-answer - none of it is hard on its own, but getting all of it to feel right takes weeks. That's why "AI chat UI kit" has become its own category. The options split into two very different kinds of thing, though, and picking the wrong kind costs more time than it saves. This is an honest comparison of six worth your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates vs. component libraries
&lt;/h2&gt;

&lt;p&gt;Before the list, the split that matters most. A chat template is a whole working app you fork: routes, layout, thread sidebar, settings, a design. A component library gives you the pieces - a message list, a prompt input, a markdown renderer - and you assemble the app yourself. Neither is better. Templates get you to a finished-looking product fastest; libraries fit into an app you already have without bringing their opinions about layout along.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token streaming&lt;/strong&gt; - text rendering as it arrives, with a stop control, not a spinner that dumps the whole reply at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citations&lt;/strong&gt; - inline markers you can click to see the quoted source, not a decorative list of links at the bottom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A canvas or artifact panel&lt;/strong&gt; - generated code or documents open beside the chat, the way ChatGPT Canvas and Claude Artifacts work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A model seam&lt;/strong&gt; - one obvious place to point the UI at a real model, without rewriting the components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend honesty&lt;/strong&gt; - whether chat history, auth, and model calls are real, or mocked so the UI runs without keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 6 best Next.js AI chat UI kits in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Vercel Chatbot - Best free full-stack chat app
&lt;/h3&gt;

&lt;p&gt;Vercel's official open-source Chatbot template is the reference implementation for the AI SDK, and it's a real, working full-stack app: Auth.js for sign-in, Neon Serverless Postgres for chat history, Vercel Blob for file uploads, AI Gateway for model access, and a shadcn/ui interface. It also ships its own artifacts feature for working on generated content beside the chat. If you want a chatbot that is genuinely wired to a database and a model on day one, and you're happy with the stock shadcn look, this is the default answer and it costs nothing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free and open source, maintained by Vercel alongside the AI SDK&lt;/li&gt;
&lt;li&gt;Real backend: auth, Postgres chat history, file storage, AI Gateway&lt;/li&gt;
&lt;li&gt;Needs a database and an AI Gateway key (or Vercel OIDC) to run&lt;/li&gt;
&lt;li&gt;Design is clean but generic shadcn/ui - expect to restyle it to make it yours&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Relay - Best designed chat UI to fork
&lt;/h3&gt;

&lt;p&gt;Relay is our entry, and it sits in the template column: a complete threaded chat app (home, /app, per-thread routes, settings, login and signup) built on Next.js 16, React 19, and Tailwind v4. Where it differs from Vercel's template is that the interface is the product. Token-by-token streaming with a working stop button, inline citation markers that open a popover with the quoted passage and source link, a Sources card under each reply, and a canvas: give a message an artifact and it opens in a side panel with a live sandboxed preview for HTML (&lt;code&gt;sandbox="allow-scripts"&lt;/code&gt; without &lt;code&gt;allow-same-origin&lt;/code&gt;, so the preview can run its own scripts but can't read the app's cookies or storage) and a read-only code view for everything else. It also ships a working Cmd+K, Markdown export, share, and a model picker that uses plain &lt;code&gt;"provider/model"&lt;/code&gt; strings in the AI Gateway style.&lt;/p&gt;

&lt;p&gt;The tradeoff, stated plainly: Relay is a UI kit, not a SaaS starter or a full-stack app. The streaming is real UI behavior running on canned replies - a timer in one hook reveals mock messages a few tokens at a time, and threads are stored in localStorage. No model is called and no key is needed, which is why the whole thing runs the moment you install it. To go live, you replace that one hook's timer with a streamed call to your endpoint; the components only read the hook's output, so nothing else changes. If you want Postgres and auth already connected, Vercel Chatbot is the better pick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/use-chat-stream.ts - the one file you change to go live&lt;/span&gt;
&lt;span class="c1"&gt;// Today: a timer reveals canned segments from mock-data.ts.&lt;/span&gt;
&lt;span class="c1"&gt;// Live: read a ReadableStream from your endpoint instead&lt;/span&gt;
&lt;span class="c1"&gt;// (for example an AI SDK route using an AI Gateway model string).&lt;/span&gt;
&lt;span class="c1"&gt;// ChatWindow only reads visibleSegments and isStreaming,&lt;/span&gt;
&lt;span class="c1"&gt;// so the UI does not change when the source of tokens does.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates/relay" rel="noopener noreferrer"&gt;See Relay&lt;/a&gt;&lt;/strong&gt; - token streaming, citation popovers, a sandboxed code canvas, and a full threaded chat app. Runs with zero keys, swaps to a real model in one hook. From $79, one-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI Elements - Best component library for AI SDK projects
&lt;/h3&gt;

&lt;p&gt;AI Elements is Vercel's open-source component library for AI interfaces, built on shadcn/ui and designed to pair with the AI SDK's &lt;code&gt;useChat&lt;/code&gt; hook. You install it through its CLI or the shadcn registry, and it copies real source files into your project - Conversation, Message, PromptInput, Reasoning, Tool, and many more - so you own and can edit every line. If you're already on shadcn/ui and the AI SDK, this is the most natural way to add a chat surface to an existing app. What it doesn't give you is the app: no thread sidebar, no routes, no layout decisions made for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. assistant-ui - Best for fully custom chat UX
&lt;/h3&gt;

&lt;p&gt;assistant-ui is an MIT-licensed TypeScript/React library of composable chat primitives, with an API modeled on shadcn/ui and cmdk. The library handles the fiddly parts - auto-scrolling, streaming state, accessibility - while you control every pixel of the look. It integrates with the Vercel AI SDK and LangGraph, works with custom backends, and supports generative UI, where tool calls render as your own components. There's also an optional hosted service, assistant-cloud, for chat history. Pick it when your chat UX is unusual enough that a finished template would fight you.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. CopilotKit - Best for an in-app copilot
&lt;/h3&gt;

&lt;p&gt;CopilotKit solves a slightly different problem: adding an AI copilot inside an existing app rather than building a chat product. Its MIT-licensed React UI package ships three ready layouts - &lt;code&gt;CopilotChat&lt;/code&gt; (embedded), &lt;code&gt;CopilotPopup&lt;/code&gt;, and &lt;code&gt;CopilotSidebar&lt;/code&gt; - plus a headless mode, and the wider framework is built around letting agents read app state and drive generative UI. If your product is a dashboard or editor that needs an assistant in the corner, this fits better than any full-page chat template.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. prompt-kit - Best lightweight shadcn add-ons
&lt;/h3&gt;

&lt;p&gt;prompt-kit is an open-source set of AI chat components built on shadcn/ui: a prompt input, an auto-scrolling chat container, a markdown renderer, a Shiki-highlighted code block, a response-stream component, and a few more. It's smaller in scope than AI Elements, which is the point - grab the two or three pieces you need, install them with the shadcn CLI, and keep everything else your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-side comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kit&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Citations / canvas&lt;/th&gt;
&lt;th&gt;Runs without keys&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vercel Chatbot&lt;/td&gt;
&lt;td&gt;Full app&lt;/td&gt;
&lt;td&gt;Auth, Postgres, Blob, AI Gateway&lt;/td&gt;
&lt;td&gt;Artifacts&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relay&lt;/td&gt;
&lt;td&gt;Full app (UI kit)&lt;/td&gt;
&lt;td&gt;Mocked, swap one hook&lt;/td&gt;
&lt;td&gt;Citation popovers + sandboxed canvas&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;From $79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Elements&lt;/td&gt;
&lt;td&gt;Component library&lt;/td&gt;
&lt;td&gt;Bring your own (AI SDK)&lt;/td&gt;
&lt;td&gt;Components to assemble&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;assistant-ui&lt;/td&gt;
&lt;td&gt;Component library&lt;/td&gt;
&lt;td&gt;Bring your own&lt;/td&gt;
&lt;td&gt;Build your own&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Free (optional cloud)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CopilotKit&lt;/td&gt;
&lt;td&gt;In-app copilot&lt;/td&gt;
&lt;td&gt;Bring your own&lt;/td&gt;
&lt;td&gt;Not the focus&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Free (MIT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prompt-kit&lt;/td&gt;
&lt;td&gt;Component library&lt;/td&gt;
&lt;td&gt;Bring your own&lt;/td&gt;
&lt;td&gt;Not included&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You want a free chatbot that's really wired to a database and a model on day one - &lt;strong&gt;Vercel Chatbot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You want a finished, distinctive chat product UI with streaming, citations, and a canvas, and you'll connect your own model - &lt;strong&gt;Relay&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You already have a shadcn/ui + AI SDK app and want to add a chat surface - &lt;strong&gt;AI Elements&lt;/strong&gt;, or &lt;strong&gt;prompt-kit&lt;/strong&gt; for something lighter.&lt;/li&gt;
&lt;li&gt;Your chat UX is unusual and you want primitives, not opinions - &lt;strong&gt;assistant-ui&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You're adding an assistant to an existing dashboard or editor, not building a chat product - &lt;strong&gt;CopilotKit&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;These aren't always either/or. A common pattern is to fork a template for the product shell and design, then use the AI SDK on the server for the actual model call. Relay's stream hook is built for exactly that swap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates/relay" rel="noopener noreferrer"&gt;See Relay&lt;/a&gt;&lt;/strong&gt; - the Next.js AI chat UI kit. Streaming replies, citation popovers, a sandboxed code canvas, Cmd+K, export, and a full threaded chat app. Light and dark, zero config to run.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Every TheKitBase Template Ships AI-Ready: CLAUDE.md, AGENTS.md, and .cursorrules</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:54:56 +0000</pubDate>
      <link>https://dev.to/thekitbase/every-thekitbase-template-ships-ai-ready-claudemd-agentsmd-and-cursorrules-3ioh</link>
      <guid>https://dev.to/thekitbase/every-thekitbase-template-ships-ai-ready-claudemd-agentsmd-and-cursorrules-3ioh</guid>
      <description>&lt;p&gt;Open an unfamiliar codebase in a coding agent and the first few prompts are rarely about the feature you actually want. They're spent on reconnaissance: where does the data live, what's the token system called, is this a server component by default, which file would a human on this team touch to add a page. A generic template leaves the agent to guess at all of it, and a wrong guess costs you a revert.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ships in every template
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLAUDE.md&lt;/strong&gt; - read by Claude Code on open. Commands, routes, where things live, the design-token system, conventions, how to add a page, and what not to touch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md&lt;/strong&gt; - the same instructions in the shared format a growing list of agents read, OpenAI Codex among them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.cursorrules&lt;/strong&gt; - the same instructions again, as the rules file Cursor loads for the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three carry identical content. They're generated from one shared structure per template, so the conventions stay consistent across the whole catalog rather than drifting from one template to the next depending on who wrote the docs that week. Open the project in whichever agent you use, and it loads the file it already knows how to read. Nothing to paste in, nothing to remember to attach.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in the file
&lt;/h2&gt;

&lt;p&gt;Not a generic AI disclaimer. Here's a trimmed, real excerpt from Stockwell's CLAUDE.md, our largest storefront template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Commands&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="sb"&gt;`npm run dev`&lt;/span&gt; - local dev server
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`npm run build`&lt;/span&gt; - production build (run before finishing any change)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`npm run typecheck`&lt;/span&gt; - TypeScript check

&lt;span class="gu"&gt;## Where things live&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Routes: /, /account, /c/[category], /cart, /checkout, /deals, /p/[slug], /shop, /wishlist
&lt;span class="p"&gt;-&lt;/span&gt; Data: The whole catalogue is in src/lib/catalog.ts - categories, products, reviews, Q&amp;amp;A, sample orders.
&lt;span class="p"&gt;-&lt;/span&gt; Design tokens (colours, fonts, radii): src/app/globals.css

&lt;span class="gu"&gt;## Payments and AI&lt;/span&gt;

The shopping assistant is offline and rule-based. src/lib/assistant.ts matches keywords
against the catalogue and returns a reply with product picks. It uses no model and needs
no API key.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last section is the one we'd flag. An agent told the wrong thing about what's actually running in a template will confidently build on a false assumption, like wiring a real API key into a chat feature that's actually a keyword matcher. So the file says so, plainly, instead of staying quiet about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest AI-status table
&lt;/h2&gt;

&lt;p&gt;That same honesty carries over to &lt;a href="https://thekitbase.app/ai-ready" rel="noopener noreferrer"&gt;/ai-ready&lt;/a&gt;, a page that lists every template's actual AI status next to its agent files - which ones run a real model, which run a rule-based or canned-demo assistant, and which have no AI features at all. It exists because a few templates in the catalog do ship mock, rule-based, or demo-corpus AI for the preview experience, and a buyer's coding agent deserves to be told that directly rather than find out by reading the source after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  A prompt cookbook, too
&lt;/h2&gt;

&lt;p&gt;Each template also gets its own cookbook page at &lt;code&gt;/templates/[slug]/ai-prompts&lt;/code&gt; - three ready-made prompts for the customizations people actually make first: changing the brand colour, adding a page, wiring up a real backend. Each prompt names the exact file it changes, so it's copy-paste, not guesswork.&lt;/p&gt;

&lt;p&gt;The result: open a template in Claude Code, Cursor, or Codex, and it already knows the answers to the first five questions it would otherwise have had to ask you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thekitbase.app/ai-ready" rel="noopener noreferrer"&gt;See every template's agent files and AI status&lt;/a&gt;, or &lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;browse the full catalog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>claudecode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I gave away the docs setup from my paid Next.js template</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 29 Sep 2026 05:15:07 +0000</pubDate>
      <link>https://dev.to/thekitbase/i-gave-away-the-docs-setup-from-my-paid-nextjs-template-1f31</link>
      <guid>https://dev.to/thekitbase/i-gave-away-the-docs-setup-from-my-paid-nextjs-template-1f31</guid>
      <description>&lt;p&gt;I sell a Next.js documentation template called DocsKit. A few weeks ago I sat down and pulled the whole marketing homepage, search, and syntax highlighting stack out of it, kept everything else, and put it on GitHub for free.&lt;/p&gt;

&lt;p&gt;Not a demo. Not a 3 page teaser that stops working when you try to add a fourth page. The actual docs system, MIT licensed, clone and go.&lt;/p&gt;

&lt;p&gt;I did this mostly because I was annoyed at how most "free tier" templates work. You clone them, poke around for five minutes, hit a wall almost immediately, and the wall is obviously there on purpose. It feels bad even when I'm the one doing the poking. So when I built the free version of mine, I just tried to answer one question honestly. What can this thing do without the stuff that actually took me real time to build?&lt;/p&gt;

&lt;p&gt;Turns out the answer is a lot. You get MDX, a sidebar, a table of contents that tracks your scroll position, dark and light mode, all four components, callout boxes, code blocks, numbered steps, and tabs. It also exports to static HTML, so you can throw it on Vercel, Netlify, wherever.&lt;/p&gt;

&lt;p&gt;What you don't get is search, which uses Pagefind and took a while to set up properly, syntax highlighting for code blocks, which uses Shiki, same story, and the actual marketing homepage that comes with the paid version. Those are the parts I'd charge for even if I open sourced everything else, because that's where most of the time went.&lt;/p&gt;

&lt;p&gt;If you want to see exactly what's different, that's the nice part about doing it this way. You can just diff the two repos. I'm not going to pretend there's nothing to compare.&lt;/p&gt;

&lt;p&gt;Demo: &lt;a href="https://docskit-free.vercel.app/" rel="noopener noreferrer"&gt;https://docskit-free.vercel.app/&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/thekitbase/docskit-free" rel="noopener noreferrer"&gt;https://github.com/thekitbase/docskit-free&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/thekitbase/docskit-free.git my-docs
&lt;span class="nb"&gt;cd &lt;/span&gt;my-docs
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's useful to you as is, great. That was the point. If you outgrow it, the paid version is linked from the repo.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Cut Your AI/LLM Costs in 2026: Caching, Cheaper Models, and Multi-Agent Routing</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:57:17 +0000</pubDate>
      <link>https://dev.to/thekitbase/how-to-cut-your-aillm-costs-in-2026-caching-cheaper-models-and-multi-agent-routing-41dp</link>
      <guid>https://dev.to/thekitbase/how-to-cut-your-aillm-costs-in-2026-caching-cheaper-models-and-multi-agent-routing-41dp</guid>
      <description>&lt;p&gt;AI features ship fast and then the bill arrives. The good news: most LLM spend is avoidable waste - the same prompt paid for a thousand times, a frontier model doing work a cheap one could handle, tokens generated that nobody reads. Here are six levers that cut real money, ordered by how much they typically save, with numbers and code you can paste today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 1: Prompt caching (the biggest single win)
&lt;/h2&gt;

&lt;p&gt;Most AI apps resend the same large prefix on every request - a system prompt, tool definitions, a knowledge base, few-shot examples. That prefix is often 80% of your input tokens, and you are paying full price for it every single time. Prompt caching bills the cached prefix at roughly one tenth of the input price on a hit. Writing to the cache costs about 1.25x, so the break-even is two requests - after that it is close to 90% off the cached portion.&lt;/p&gt;

&lt;p&gt;The rule is that caching is a prefix match: anything before your cache breakpoint must be byte-identical across requests. Put the stable content first (frozen system prompt, deterministic tool list) and the volatile content (the user's actual question, timestamps, per-request ids) last. A single &lt;code&gt;datetime.now()&lt;/code&gt; interpolated into the system prompt invalidates the entire cache on every request - the most common reason a cache hit rate sits at zero.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fastest audit: log &lt;code&gt;cache_read_input_tokens&lt;/code&gt; on every response. If it is zero across repeated requests with the same system prompt, a silent invalidator (a timestamp, a random id, unsorted JSON) is in your prefix. Find it and you just cut input cost by up to 90%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lever 2: Right-size the model
&lt;/h2&gt;

&lt;p&gt;The reflex to route everything to the smartest, most expensive model is the second biggest source of waste. Model tiers differ by 5x or more in price, and most requests in a real app - classification, extraction, short answers, routing - do not need the top tier. The trick is matching the model to the job, not to your ambition.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Rough price / 1M tokens (in / out)&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small (Haiku-class)&lt;/td&gt;
&lt;td&gt;~$1 / ~$5&lt;/td&gt;
&lt;td&gt;Classification, extraction, routing, short replies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid (Sonnet-class)&lt;/td&gt;
&lt;td&gt;~$3 / ~$15&lt;/td&gt;
&lt;td&gt;Most product features, tool-calling agents, coding help&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontier (Opus-class)&lt;/td&gt;
&lt;td&gt;~$5 / ~$25&lt;/td&gt;
&lt;td&gt;Hard reasoning, long-horizon agents, the few requests that need it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Prices move, so check current rates - but the ratios hold: the small tier is roughly 5x cheaper than frontier for input and output alike. And do not sleep on open-source models. Llama, Mistral, DeepSeek, and Qwen are available through the same gateway, and for high-volume, well-scoped tasks (summarization, extraction, internal tools) they can undercut hosted frontier models dramatically. Route the boring 80% of your traffic to a cheap or open model and reserve the expensive tier for the requests that actually need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 3: Multi-agent routing (the cascade pattern)
&lt;/h2&gt;

&lt;p&gt;Levers 1 and 2 combine into the highest-leverage architecture: a cheap model classifies each incoming request, and only the genuinely hard ones escalate to an expensive model. This is the cascade - a fast, cheap triage step in front of your real work. In most apps the classifier costs a rounding error and diverts the majority of traffic away from the frontier tier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. A cheap model triages the request&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;difficulty&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="nf"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic/claude-haiku-4.5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Classify as SIMPLE or HARD. Reply with one word only.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Route to the tier that fits&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HARD&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;anthropic/claude-sonnet-4.6&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;anthropic/claude-haiku-4.5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same idea scales up: specialized agents per job (a cheap extractor, a mid-tier writer, a frontier planner) instead of one expensive generalist doing everything. You pay top-tier prices only for the steps that are genuinely top-tier work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 4: Response and semantic caching
&lt;/h2&gt;

&lt;p&gt;Prompt caching (Lever 1) discounts the shared prefix. Response caching skips the model call entirely when the same request comes in again. For static-knowledge queries - FAQs, policy lookups, translations, extraction from identical documents - cache the whole response at the gateway and serve repeats for free.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai/gpt-5.4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summarize our refund policy in two sentences.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;providerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cacheControl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max-age=3600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// serve repeats for 1 hour&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;The cache key is the model plus the prompt plus the generation parameters, so identical requests hit and anything that varies (a real conversation) misses and is billed normally. Do not cache user-specific conversations; do cache the repetitive, deterministic lookups that make up a surprising share of most apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 5: Gateway-level cost controls
&lt;/h2&gt;

&lt;p&gt;Routing every call through a single gateway is what makes the levers above governable instead of theoretical. From one place you get spend attribution by feature and user, automatic failover to a cheaper provider or model when the primary is down or rate-limited, per-user rate limits so one abuser cannot run up your bill, and budget alerts before you blow the month.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gateway&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic/claude-sonnet-4.6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;providerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic&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;bedrock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;       &lt;span class="c1"&gt;// failover order&lt;/span&gt;
      &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai/gpt-5.4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;            &lt;span class="c1"&gt;// fallback model if the primary fails&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                           &lt;span class="c1"&gt;// per-user tracking and rate limits&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feature:support&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;env:prod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// cost attribution&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;Because the gateway charges provider list price with zero markup, you get all of this - failover, tracking, per-user limits, budgets - without paying more per token than calling the provider directly. Tags turn a mystery invoice into a per-feature cost breakdown, which is how you find the one endpoint quietly burning half your budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lever 6: Token hygiene
&lt;/h2&gt;

&lt;p&gt;The unglamorous lever that adds up. You pay per token in and per token out, so trim both.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use structured output (a schema) instead of asking for JSON in prose - shorter, parseable, and no wasted tokens explaining the format.&lt;/li&gt;
&lt;li&gt;Cap output with &lt;code&gt;maxOutputTokens&lt;/code&gt;. A model with no ceiling will happily write three paragraphs where you needed one sentence.&lt;/li&gt;
&lt;li&gt;Trim the context you resend - old tool results and stale history are pure cost. Summarize or drop them.&lt;/li&gt;
&lt;li&gt;For anything not real-time (nightly reports, bulk classification, embeddings backfills), use the Batch API - it runs asynchronously at roughly half price.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;None of these are exotic. Stacked, they routinely take an AI feature from alarming to boring on the cost side. A representative before-and-after for a support assistant handling a mix of simple and hard questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Relative cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Everything to the frontier model, full prefix every request&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ prompt caching on the system prompt and tools&lt;/td&gt;
&lt;td&gt;~40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ cascade routing (cheap triage, escalate only hard ones)&lt;/td&gt;
&lt;td&gt;~15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ response caching on repeated lookups + output caps&lt;/td&gt;
&lt;td&gt;~10%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact numbers depend on your traffic mix, but the shape is real: the same product, an order of magnitude cheaper to run, with no drop in quality on the requests that actually matter - because the frontier model still handles those. The waste you cut was never doing useful work.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://thekitbase.app/templates/keel-saas-starter-kit" rel="noopener noreferrer"&gt;Keel&lt;/a&gt;, our AI SaaS starter kit, is built cost-aware from the start - gateway routing, model fallbacks, and a streaming assistant wired so you can swap tiers with a string. Start from an architecture that already respects your bill.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>webdev</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Tool Calling with the AI SDK in Next.js: Build an Assistant That Actually Does Things</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:55:20 +0000</pubDate>
      <link>https://dev.to/thekitbase/tool-calling-with-the-ai-sdk-in-nextjs-build-an-assistant-that-actually-does-things-3kh2</link>
      <guid>https://dev.to/thekitbase/tool-calling-with-the-ai-sdk-in-nextjs-build-an-assistant-that-actually-does-things-3kh2</guid>
      <description>&lt;p&gt;A chat box that only answers questions is a demo. The moment your AI feature has to look something like a user's orders, book a slot, or update a record, you need tool calling - letting the model call functions you define, read the results, and keep going until the task is done. This guide builds that loop with the Vercel AI SDK v6 in Next.js, and it is shorter than you would expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;You give the model a set of tools - each one a description, an input schema, and a function to run. The model decides which tools to call and with what arguments; the SDK runs your functions, feeds the results back, and loops until the model produces a final answer. You never write the orchestration loop by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: one package, gateway model strings
&lt;/h2&gt;

&lt;p&gt;Install the &lt;code&gt;ai&lt;/code&gt; package. For the model, pass a plain provider/model string and it routes through the Vercel AI Gateway automatically - no provider SDK, no API key juggling, and you can swap models by changing the string. Gateway slugs use dots for versions (&lt;code&gt;anthropic/claude-sonnet-4.6&lt;/code&gt;), which is the one naming gotcha to remember.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/chat/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;prompt&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;result&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;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic/claude-sonnet-4.6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// routes through AI Gateway&lt;/span&gt;
    &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining a tool
&lt;/h2&gt;

&lt;p&gt;A tool is a description (so the model knows when to reach for it), an &lt;code&gt;inputSchema&lt;/code&gt; built with Zod (so arguments are validated and typed), and an &lt;code&gt;execute&lt;/code&gt; function (your actual code). Be prescriptive in the description about when to call it - not just what it does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tool&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&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;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getOrders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Look up a customer's recent orders. Call this whenever the user asks about order status, history, or a specific purchase.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The customer id from the session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How many orders to return&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;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;orders&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;blockquote&gt;
&lt;p&gt;In AI SDK v6 the field is &lt;code&gt;inputSchema&lt;/code&gt;, not &lt;code&gt;parameters&lt;/code&gt;. If you are following an older tutorial that uses &lt;code&gt;parameters&lt;/code&gt;, it will not typecheck - the rename is the single most common upgrade error.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The multi-step loop
&lt;/h2&gt;

&lt;p&gt;One tool call is rarely the whole task. The model might call &lt;code&gt;getOrders&lt;/code&gt;, read the result, then decide it also needs to check inventory before answering. &lt;code&gt;stopWhen&lt;/code&gt; lets the SDK run that back-and-forth automatically - it keeps looping (call tool, feed result back, let the model continue) until the model stops calling tools or you hit the step cap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepCountIs&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;generateText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic/claude-sonnet-4.6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stopWhen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;stepCountIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// cap the loop at 5 steps&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;checkInventory&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Where is my last order and is a replacement in stock?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// result.text is the final answer after all tool calls resolved&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;stepCountIs(5)&lt;/code&gt; is your safety belt. Without a cap, a confused model could loop indefinitely. Five is a reasonable default for most assistants; raise it for genuinely multi-hop tasks, lower it for simple lookups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming the whole thing to the UI
&lt;/h2&gt;

&lt;p&gt;For a chat interface you want tokens and tool activity to stream, not arrive in one blob. Swap &lt;code&gt;generateText&lt;/code&gt; for &lt;code&gt;streamText&lt;/code&gt; - same tools, same &lt;code&gt;stopWhen&lt;/code&gt; - and pipe it to the client. The user sees the assistant think, call a tool, and answer in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stepCountIs&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;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic/claude-sonnet-4.6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stopWhen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;stepCountIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;checkInventory&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUIMessageStreamResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gating the dangerous tools
&lt;/h2&gt;

&lt;p&gt;Read-only tools (look up orders, search docs) can run automatically. Tools with side effects - refund a payment, delete a record, send an email - should not fire without a human in the loop. The cleanest pattern is to make the &lt;code&gt;execute&lt;/code&gt; function return a proposal instead of performing the action, then confirm on the client before a second call actually commits it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate every argument at the boundary - the model's output is untrusted input, so treat &lt;code&gt;customerId&lt;/code&gt; like anything else a user could send.&lt;/li&gt;
&lt;li&gt;For destructive actions, have &lt;code&gt;execute&lt;/code&gt; return what it would do and surface a confirm step in the UI before committing.&lt;/li&gt;
&lt;li&gt;Scope your tools to the current session - never let the model pass in a &lt;code&gt;customerId&lt;/code&gt; that is not the authenticated user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  From tools to a reusable agent
&lt;/h2&gt;

&lt;p&gt;Once you have a set of tools you reuse across routes, wrap them in an agent instead of re-declaring the config each time. The AI SDK's &lt;code&gt;ToolLoopAgent&lt;/code&gt; pattern bundles the model, tools, and &lt;code&gt;stopWhen&lt;/code&gt; into one object you can call from anywhere and type end-to-end. Same loop, less repetition.&lt;/p&gt;

&lt;p&gt;The whole point of tool calling: your assistant stops being a search box with personality and becomes a teammate that can actually operate your product - safely, because you define exactly what it can touch.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://thekitbase.app/templates/keel-saas-starter-kit" rel="noopener noreferrer"&gt;Keel&lt;/a&gt;, our AI SaaS starter kit, ships with a streaming assistant, tool-calling wired through the AI Gateway, and the auth and billing around it - so you start from a working agent instead of a blank route handler.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Next.js 16 Cache Components: use cache, PPR, and When to Reach for Each</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:46:38 +0000</pubDate>
      <link>https://dev.to/thekitbase/nextjs-16-cache-components-use-cache-ppr-and-when-to-reach-for-each-503e</link>
      <guid>https://dev.to/thekitbase/nextjs-16-cache-components-use-cache-ppr-and-when-to-reach-for-each-503e</guid>
      <description>&lt;p&gt;Next.js 16 shipped Cache Components - the feature that finally lets a single route mix static HTML, cached data, and per-request dynamic content without splitting it into separate pages. It is Partial Prerendering (PPR) made stable, plus a new &lt;code&gt;use cache&lt;/code&gt; directive that replaces the old &lt;code&gt;unstable_cache&lt;/code&gt; and the awkward route-segment config flags. This guide covers what changed, the three content types you now think in, and the runtime-data rule that trips up almost everyone on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;If you were using the experimental PPR flag, it is gone. Cache Components is a single config switch, and it turns on the whole model - static shell, cached segments, and streamed dynamic content in one route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// next.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&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;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cacheComponents&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="c1"&gt;// replaces experimental.ppr&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it is on, every piece of your route falls into one of three buckets. The whole mental model is learning which bucket each component belongs in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three content types
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static&lt;/strong&gt; - synchronous code, imports, and pure markup. Prerendered at build time and served instantly from the CDN. Your header, nav, and layout shell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cached&lt;/strong&gt; - async data that does not need to be fresh on every request. Marked with &lt;code&gt;use cache&lt;/code&gt;. Think product lists, blog posts, dashboard stats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic&lt;/strong&gt; - runtime data that must be fresh (cookies, headers, per-user state). Wrapped in Suspense so it streams in after the shell paints.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;Suspense&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&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;next/headers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cacheLife&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;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Static - instant from the CDN */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Dashboard&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Cached - fast, revalidates hourly */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Stats&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Dynamic - streams in with fresh data */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NotificationsSkeleton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Notifications&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Suspense&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nf"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hours&lt;/span&gt;&lt;span class="dl"&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;stats&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StatsDisplay&lt;/span&gt; &lt;span class="na"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Notifications&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;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cookies&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;userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NotificationList&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payoff: the user sees the static shell and cached stats instantly, and the personalized notifications stream in a beat later - instead of the whole page blocking on the slowest query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The use cache directive
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;use cache&lt;/code&gt; works at three levels - the whole file, a single component, or a plain data function. Put it at the top of whatever scope you want cached, and Next.js generates the cache key automatically from the function's arguments and closure variables. No manual key arrays like the old unstable_cache required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Function level - cache a query&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Component level - cache a whole subtree&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PricingTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plans&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plans&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Plans&lt;/span&gt; &lt;span class="na"&gt;plans&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;plans&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controlling lifetime with cacheLife
&lt;/h2&gt;

&lt;p&gt;By default a &lt;code&gt;use cache&lt;/code&gt; block is stale after 5 minutes and revalidates after 15. Override that with a built-in profile or an inline config. The built-in profiles are default, minutes, hours, days, weeks, and max.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cacheLife&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;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nf"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// serve stale for 1 hour while revalidating&lt;/span&gt;
    &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// refresh in the background every 2 hours&lt;/span&gt;
    &lt;span class="na"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// hard expiry after 1 day&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/data&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;h2&gt;
  
  
  Invalidation: cacheTag, updateTag, revalidateTag
&lt;/h2&gt;

&lt;p&gt;Tag your cached data, then invalidate by tag from a Server Action. The one distinction that matters: &lt;code&gt;updateTag&lt;/code&gt; refreshes within the same request (the user who just edited sees fresh data immediately), while &lt;code&gt;revalidateTag&lt;/code&gt; is background-only (the next visitor sees fresh data). Reach for &lt;code&gt;updateTag&lt;/code&gt; after a mutation the current user made; reach for &lt;code&gt;revalidateTag&lt;/code&gt; for everyone else's writes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cacheTag&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;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nf"&gt;cacheTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&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;product-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In a Server Action:&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;updateTag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;revalidateTag&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;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;editProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;FormData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&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="nf"&gt;updateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// same request sees the change&lt;/span&gt;
  &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// everyone else's next load is fresh&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The rule that trips everyone up
&lt;/h2&gt;

&lt;p&gt;You cannot call &lt;code&gt;cookies()&lt;/code&gt;, &lt;code&gt;headers()&lt;/code&gt;, or read &lt;code&gt;searchParams&lt;/code&gt; inside a &lt;code&gt;use cache&lt;/code&gt; block. It makes sense once you say it out loud - cached output has to be identical for everyone, and those APIs are per-request. The fix is to read the dynamic value outside the cached function and pass it in as an argument, where it becomes part of the cache key automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Wrong - runtime API inside use cache&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CachedProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cookies&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;session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="c1"&gt;// Error&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Right - read outside, pass in as an argument&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfilePage&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;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cookies&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;session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CachedProfile&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CachedProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// sessionId is part of the key&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you genuinely cannot refactor (a compliance wrapper, say), &lt;code&gt;use cache: private&lt;/code&gt; is the escape hatch - it permits runtime APIs and caches per-user. Use it sparingly; it is not a shared cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from the old world
&lt;/h2&gt;

&lt;p&gt;If you are coming from an earlier Next.js app, most of the migration is deleting things. Here is the direct mapping.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Old&lt;/th&gt;
&lt;th&gt;Next.js 16 replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;experimental.ppr&lt;/td&gt;
&lt;td&gt;cacheComponents: true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;export const dynamic = 'force-dynamic'&lt;/td&gt;
&lt;td&gt;Remove - it is the default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;export const dynamic = 'force-static'&lt;/td&gt;
&lt;td&gt;'use cache' + cacheLife('max')&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;export const revalidate = N&lt;/td&gt;
&lt;td&gt;cacheLife({ revalidate: N })&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unstable_cache()&lt;/td&gt;
&lt;td&gt;'use cache' directive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unstable_cache migration is the big one, and it is mostly deletion: no more manual key arrays, tags move from an options object into &lt;code&gt;cacheTag()&lt;/code&gt; calls, and revalidate moves into &lt;code&gt;cacheLife()&lt;/code&gt;. The same cookies/headers restriction that applied to unstable_cache applies to &lt;code&gt;use cache&lt;/code&gt;, so any code that already respected it ports cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations worth knowing up front
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js runtime only - Cache Components does not run on the Edge runtime.&lt;/li&gt;
&lt;li&gt;No static export - you need a server, so &lt;code&gt;next export&lt;/code&gt; is out.&lt;/li&gt;
&lt;li&gt;Non-deterministic values (Math.random, Date.now) run once at build time inside a cached block. For per-request randomness, call &lt;code&gt;connection()&lt;/code&gt; from next/server to defer to request time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to reach for each
&lt;/h2&gt;

&lt;p&gt;The decision is almost always about freshness. Does the data change per-user or per-request? Dynamic, wrapped in Suspense. Does it change occasionally and can be a few minutes stale? Cached with &lt;code&gt;use cache&lt;/code&gt; and a cacheLife that matches your tolerance. Never changes between deploys? Leave it static. Get those three buckets right and you get CDN-fast first paint with correct, fresh data streaming in - which used to require picking one or the other.&lt;/p&gt;




&lt;p&gt;Every &lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;TheKitBase&lt;/a&gt; template is built on Next.js 16 with this rendering model already wired up - static shells, cached data layers, and streamed dynamic content, so you inherit the fast-by-default architecture instead of retrofitting it.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>The Best Free Next.js Templates in 2026 (Sorted by Use Case)</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:14:16 +0000</pubDate>
      <link>https://dev.to/thekitbase/the-best-free-nextjs-templates-in-2026-sorted-by-use-case-85b</link>
      <guid>https://dev.to/thekitbase/the-best-free-nextjs-templates-in-2026-sorted-by-use-case-85b</guid>
      <description>&lt;p&gt;There are a lot of free Next.js templates out there, and most "best free templates" lists are just a wall of links with no guidance. This one is sorted by what you're actually building, with the genuinely good free options in each category and an honest note on where free stops being enough. Every pick here is free to use; the premium upgrades are called out where they save real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find free templates first
&lt;/h2&gt;

&lt;p&gt;Before any list, know the three sources that cover most needs and are always current because their maintainers keep them updated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The official Next.js examples repo&lt;/strong&gt; - dozens of minimal starters for specific integrations (auth, CMS, databases, i18n), maintained alongside the framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Vercel Templates directory&lt;/strong&gt; - a large gallery of free and paid templates you can deploy in one click&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shadcn/ui&lt;/strong&gt; - not a template but a component collection you copy into your project; the fastest way to a good-looking UI from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For marketing sites and landing pages
&lt;/h2&gt;

&lt;p&gt;Free marketing starters get you a hero, features, and a footer, which is enough to validate an idea. What they rarely give you is a complete, cohesive multi-page site with blog, pricing, and auth designed as one system. For a quick landing page, the Vercel and Tailwind free starters are fine; when you need a finished marketing site, that's where a premium template earns its price.&lt;/p&gt;

&lt;h2&gt;
  
  
  For dashboards and admin panels
&lt;/h2&gt;

&lt;p&gt;shadcn/ui ships an excellent free dashboard example, and it's the best free starting point for an admin UI - charts, tables, and a sidebar you can build on. The gap with free dashboards is depth: you typically get one or two screens, not the 9-12 cohesive pages a real admin panel needs (billing, team, settings, reports). Free gets you the shell; a production template gets you the whole app.&lt;/p&gt;

&lt;h2&gt;
  
  
  For e-commerce
&lt;/h2&gt;

&lt;p&gt;Next.js Commerce, Vercel's open-source storefront, is the standout free e-commerce starter - a real, deployable store wired for a headless backend. It's an excellent foundation if you're comfortable connecting a commerce backend. For a simpler, static storefront with a working cart and no backend to configure, a focused template will be faster to launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  For docs and content
&lt;/h2&gt;

&lt;p&gt;Nextra is the go-to free documentation framework on Next.js - MDX, search, and a clean default theme, all open-source. For blogs and content sites, the official Next.js blog examples and any of the many free MDX starters will do the job. Docs is one area where free options are genuinely strong.&lt;/p&gt;

&lt;h2&gt;
  
  
  For AI apps
&lt;/h2&gt;

&lt;p&gt;Vercel's open-source AI Chatbot template is the best free starting point for anything with a streaming AI interface - it's the reference implementation for the AI SDK and stays current with it. Open SaaS (on the Wasp framework) is a capable free full-stack option with AI examples. Both are great to learn from; neither is a complete, polished SaaS with the product shell around the AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  For portfolios
&lt;/h2&gt;

&lt;p&gt;Portfolios are the most abundant category of free template - GitHub is full of open-source Next.js portfolio starters, and many are genuinely good. If you want something that stands out from the common grey-on-white developer portfolio, that's where a designed premium template is worth it, but for a solid free portfolio you're spoiled for choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  When free is enough (and when it isn't)
&lt;/h2&gt;

&lt;p&gt;Free templates are perfect for learning, prototyping, side projects, and anything where your time is worth less than the money saved. They stop being enough when you're shipping something people pay for and your time matters: free templates are usually shallow (a few screens, not a whole product), inconsistent (assembled from parts rather than designed as a system), and yours to finish. A good premium template is the finished system - which is the difference between a weekend of setup and an afternoon of shipping.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rule of thumb: use free templates to learn and prototype; buy a premium one the moment your time is worth more than the price. A $79 template that saves a week is one of the cheapest hours-back trades in software.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;When free stops being enough - see TheKitBase templates from $39&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>resources</category>
    </item>
    <item>
      <title>The Ultimate Next.js SaaS Tech Stack for 2026</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:10:27 +0000</pubDate>
      <link>https://dev.to/thekitbase/the-ultimate-nextjs-saas-tech-stack-for-2026-15mn</link>
      <guid>https://dev.to/thekitbase/the-ultimate-nextjs-saas-tech-stack-for-2026-15mn</guid>
      <description>&lt;p&gt;The hardest part of starting a SaaS in 2026 isn't writing code - it's choosing the stack. Every layer has five defensible options and a dozen strong opinions. This is an opinionated, boringly reliable stack for building a Next.js SaaS: the choices that let you ship fast, scale sanely, and not rewrite everything in six months. Where there's a real fork, I'll name the alternative and when to take it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;th&gt;Strong alternative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js (App Router)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript (strict)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4&lt;/td&gt;
&lt;td&gt;CSS Modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Components&lt;/td&gt;
&lt;td&gt;shadcn/ui&lt;/td&gt;
&lt;td&gt;Build your own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Auth.js&lt;/td&gt;
&lt;td&gt;Clerk (managed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Postgres (Neon) + Drizzle&lt;/td&gt;
&lt;td&gt;Supabase / Prisma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Dodo / Lemon Squeezy (MoR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Vercel AI SDK + Claude&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Resend&lt;/td&gt;
&lt;td&gt;Postmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Framework, language, and styling
&lt;/h2&gt;

&lt;p&gt;Next.js with the App Router is the default for a reason: server components, server actions, and a single codebase for your marketing site and app. TypeScript in strict mode is non-negotiable for anything you'll maintain - the compile-time safety pays for itself the first time you rename a field. Tailwind CSS v4 for styling, with its &lt;code&gt;@theme&lt;/code&gt; token system, makes an entire product reskinnable from a handful of variables and keeps styles colocated with markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth and database
&lt;/h2&gt;

&lt;p&gt;For auth, Auth.js (formerly NextAuth) is the flexible, self-hosted default; reach for Clerk when you'd rather pay to never think about auth UI, sessions, and organizations again. For data, Postgres is the safe forever-choice - Neon and Supabase both give you serverless Postgres with generous free tiers. Pair it with Drizzle (typed, SQL-first, light) or Prisma (batteries-included, more magic). The through-line: pick managed Postgres and a typed query layer, and you won't outgrow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payments
&lt;/h2&gt;

&lt;p&gt;Stripe is the default and the most capable, and the right pick if you're comfortable handling sales tax and VAT yourself (or with a tax add-on). If you'd rather offload tax and compliance entirely, a merchant-of-record provider like Dodo Payments or Lemon Squeezy handles the tax paperwork globally in exchange for a slightly higher fee - often worth it for a small team selling internationally. Decide based on whether you want to own tax compliance or rent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI, email, and hosting
&lt;/h2&gt;

&lt;p&gt;If your SaaS has an AI feature, the Vercel AI SDK is the standard way to build it - one API across providers, first-class streaming, and an easy swap between models. Point it at Claude for strong reasoning and a clean developer experience. For transactional email (receipts, invites, password resets), Resend is the modern default with a great DX and React-based email templates. And for hosting, Vercel is the path of least resistance for Next.js - zero-config deploys, preview URLs, and edge/serverless handled for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI:&lt;/strong&gt; Vercel AI SDK + Claude - streaming, provider-agnostic, swap models in one line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email:&lt;/strong&gt; Resend - transactional email with React templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting:&lt;/strong&gt; Vercel - zero-config Next.js deploys and preview URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics:&lt;/strong&gt; Vercel Analytics or PostHog when you need product analytics + flags&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The best stack is the one you don't have to think about. Every pick here is chosen to be reliable and swappable, not clever - so you spend your energy on the product, not on infrastructure decisions you'll second-guess at 2am.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates/keel-saas-starter-kit" rel="noopener noreferrer"&gt;Skip the setup - TheKitBase templates ship on exactly this stack&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>saas</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Next.js vs Astro in 2026: Which Should You Actually Use?</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:03:34 +0000</pubDate>
      <link>https://dev.to/thekitbase/nextjs-vs-astro-in-2026-which-should-you-actually-use-ke3</link>
      <guid>https://dev.to/thekitbase/nextjs-vs-astro-in-2026-which-should-you-actually-use-ke3</guid>
      <description>&lt;p&gt;Next.js and Astro get pitted against each other constantly, which is a little unfair because they were built for different jobs. Next.js is a full-stack React framework for building applications; Astro is a content-first framework for building fast, mostly-static sites. They overlap in the middle - marketing sites, blogs, docs - which is exactly where the choice gets interesting. Here's how they actually differ and a clear rule for deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core difference: apps vs. content
&lt;/h2&gt;

&lt;p&gt;Next.js assumes you're building an application - interactivity, authentication, dashboards, data that changes per user. It ships React, runs server components, supports server actions and dynamic rendering, and gives you a full backend surface. Astro assumes you're building content - articles, marketing pages, documentation - and optimizes ruthlessly for that: it ships zero JavaScript by default and only hydrates the interactive "islands" you explicitly mark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Next.js&lt;/th&gt;
&lt;th&gt;Astro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built for&lt;/td&gt;
&lt;td&gt;Full-stack apps&lt;/td&gt;
&lt;td&gt;Content-first sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default JS shipped&lt;/td&gt;
&lt;td&gt;React runtime&lt;/td&gt;
&lt;td&gt;Zero (islands opt-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendering&lt;/td&gt;
&lt;td&gt;SSR, SSG, ISR, RSC&lt;/td&gt;
&lt;td&gt;Static-first, SSR available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI framework&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;Any (React, Vue, Svelte) or none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Dashboards, SaaS, auth'd apps&lt;/td&gt;
&lt;td&gt;Blogs, docs, marketing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Performance: Astro's home turf
&lt;/h2&gt;

&lt;p&gt;For a content site with little interactivity, Astro is hard to beat on raw performance because it ships almost no JavaScript - the browser gets HTML and CSS and nothing to parse and execute. Next.js has closed much of the gap with React Server Components, which keep non-interactive components off the client, and a well-built Next.js content site scores excellently. But if your page is genuinely mostly static content, Astro's zero-JS default gives it a structural head start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Next.js pulls ahead
&lt;/h2&gt;

&lt;p&gt;The moment your product becomes an application - users log in, data is personalized, state is complex, you need a real backend - Next.js is the stronger choice. The App Router, server components, server actions, and the enormous React ecosystem are built for exactly this. Astro can do SSR and can embed React islands, but you'd be fighting its content-first grain to build a full SaaS in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule for picking
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Building a blog, docs site, or marketing site that's mostly content - &lt;strong&gt;Astro&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Building an app with auth, dashboards, and dynamic data - &lt;strong&gt;Next.js&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Building a SaaS whose marketing site and app should share one codebase and stack - &lt;strong&gt;Next.js&lt;/strong&gt; for both keeps it simple.&lt;/li&gt;
&lt;li&gt;Not sure and you might grow into an app - &lt;strong&gt;Next.js&lt;/strong&gt; is the safer long-term bet.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;It's not really a rivalry - it's a fork in the road. Content site: reach for Astro. Application: reach for Next.js. The trap is picking Astro because it benchmarks faster, then spending months bending it into an app it was never meant to be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;Building an app? Start from a production Next.js foundation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cursor vs Claude Code vs Copilot: The 2026 AI Coding Tools Compared</title>
      <dc:creator>TheKitBase</dc:creator>
      <pubDate>Tue, 14 Jul 2026 12:59:24 +0000</pubDate>
      <link>https://dev.to/thekitbase/cursor-vs-claude-code-vs-copilot-the-2026-ai-coding-tools-compared-2fg</link>
      <guid>https://dev.to/thekitbase/cursor-vs-claude-code-vs-copilot-the-2026-ai-coding-tools-compared-2fg</guid>
      <description>&lt;p&gt;AI-assisted coding stopped being a novelty and became the default way most developers work. By 2026 three tools lead the space, and they're not really the same kind of thing: Cursor is an AI-first editor, Claude Code is an agentic terminal tool, and GitHub Copilot is an assistant that lives inside your existing IDE. They overlap enough to compete and differ enough that the right pick depends on how you actually work. Here's an honest breakdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quick version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Form factor&lt;/th&gt;
&lt;th&gt;Best at&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;AI-first editor (VS Code fork)&lt;/td&gt;
&lt;td&gt;Inline editing + in-context chat&lt;/td&gt;
&lt;td&gt;Multiple (incl. Claude, GPT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Agentic terminal / CLI&lt;/td&gt;
&lt;td&gt;Autonomous multi-file changes&lt;/td&gt;
&lt;td&gt;Claude (Opus / Sonnet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;Plugin for your existing IDE&lt;/td&gt;
&lt;td&gt;Completions inside your current setup&lt;/td&gt;
&lt;td&gt;Multiple (incl. Claude, GPT)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Cursor - the AI-first editor
&lt;/h2&gt;

&lt;p&gt;Cursor is a fork of VS Code rebuilt around AI. You get everything familiar about VS Code plus deep AI integration: inline edits, a chat that knows your codebase, and an agent mode that can make multi-file changes while you watch. Because it's the editor itself, the AI has rich context about what you're looking at, and the feedback loop is tight - highlight code, describe a change, accept the diff.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Best for developers who want AI woven into the editor, not bolted on&lt;/li&gt;
&lt;li&gt;Strong inline editing and codebase-aware chat&lt;/li&gt;
&lt;li&gt;You switch editors (from VS Code) to use it&lt;/li&gt;
&lt;li&gt;Model choice, with usage-based and subscription pricing tiers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Claude Code - the agentic terminal tool
&lt;/h2&gt;

&lt;p&gt;Claude Code takes a different shape entirely: it lives in your terminal and works agentically. You describe a task in natural language and it explores the codebase, makes changes across many files, runs commands, and iterates - closer to delegating to a capable pair than autocompleting your typing. It's editor-agnostic (your files are just files), which makes it a strong fit for larger, multi-step changes and for developers who live in the terminal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Best for autonomous, multi-file tasks and refactors you can describe end to end&lt;/li&gt;
&lt;li&gt;Editor-agnostic - works alongside whatever you already use&lt;/li&gt;
&lt;li&gt;Terminal-native, scriptable, and strong at planning across a codebase&lt;/li&gt;
&lt;li&gt;Runs on Claude models; available via subscription and API-based usage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Copilot - the assistant in your IDE
&lt;/h2&gt;

&lt;p&gt;Copilot is the incumbent and the least disruptive to adopt: it's a plugin for the IDE you already use (VS Code, JetBrains, and others), so nothing about your setup changes. It started as autocomplete and has grown chat and agent capabilities, plus tight integration with GitHub itself - pull requests, issues, and code review. If you don't want to change editors and you live in the GitHub ecosystem, it's the path of least resistance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Best for staying in your current IDE with zero workflow change&lt;/li&gt;
&lt;li&gt;Deep GitHub integration (PRs, issues, review)&lt;/li&gt;
&lt;li&gt;Completions, chat, and agent mode across many editors&lt;/li&gt;
&lt;li&gt;Predictable per-seat subscription pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Want AI as the center of your editor and you'll switch from VS Code - &lt;strong&gt;Cursor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Want to delegate whole multi-file tasks from the terminal - &lt;strong&gt;Claude Code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Want to keep your exact IDE and GitHub workflow - &lt;strong&gt;Copilot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Honestly, many developers run two: a completion/edit tool in the editor and an agentic tool for big changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Whichever you pick, the same rule applies: AI tools accelerate work within a structure far better than they invent structure from nothing. Start from a real, well-architected foundation and any of these tools becomes dramatically more useful - the coherence is already there for them to build on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://thekitbase.app/templates" rel="noopener noreferrer"&gt;Give your AI tool a real foundation - browse TheKitBase templates&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
