<?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: Malloc72P</title>
    <description>The latest articles on DEV Community by Malloc72P (@malloc72p).</description>
    <link>https://dev.to/malloc72p</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3844203%2F5e795bd4-b5f9-4a39-81e1-3117fea85d8a.jpg</url>
      <title>DEV Community: Malloc72P</title>
      <link>https://dev.to/malloc72p</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/malloc72p"/>
    <language>en</language>
    <item>
      <title>Frontend Monorepo Architecture: A Practical Guide with pnpm Workspaces and Turborepo</title>
      <dc:creator>Malloc72P</dc:creator>
      <pubDate>Wed, 15 Apr 2026 01:22:26 +0000</pubDate>
      <link>https://dev.to/malloc72p/frontend-monorepo-architecture-a-practical-guide-with-pnpm-workspaces-and-turborepo-4dbk</link>
      <guid>https://dev.to/malloc72p/frontend-monorepo-architecture-a-practical-guide-with-pnpm-workspaces-and-turborepo-4dbk</guid>
      <description>&lt;p&gt;Note: This post is a translated version of an article originally published on my personal blog. You can read the &lt;a href="https://blog.malloc72p.com/posts/frontend/monorepo" rel="noopener noreferrer"&gt;original Korean post here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a Monorepo?
&lt;/h1&gt;

&lt;p&gt;A monorepo is an approach where multiple projects are managed within a single repository. This helps maintain code consistency, improves reusability, and simplifies dependency management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Characteristics of a Monorepo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent codebase&lt;/strong&gt;: Since all projects live in the same repository, you can enforce consistent code style and conventions across all of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code reusability&lt;/strong&gt;: Common modules and libraries can be easily shared and reused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency management&lt;/strong&gt;: Dependencies between projects are much easier to manage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified build and deployment&lt;/strong&gt;: You can build and deploy the entire project at once, making operations more efficient.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What if You Don't Use a Monorepo?
&lt;/h2&gt;

&lt;p&gt;The approach where each repository contains exactly one project is called a &lt;strong&gt;polyrepo&lt;/strong&gt;. For simple projects, polyrepo can actually be easier to manage — but as projects grow and become more complex, several pain points emerge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code duplication&lt;/strong&gt;: Common modules tend to get duplicated across multiple repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency conflicts&lt;/strong&gt;: Different repositories may use different versions of the same library, leading to conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistency&lt;/strong&gt;: Code style and conventions can differ from repo to repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex builds and deployments&lt;/strong&gt;: Each repository must be built and deployed individually, making management complicated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code duplication can be addressed by publishing shared modules to npm or GitHub Packages, but that introduces additional overhead for versioning and release management.&lt;/p&gt;

&lt;p&gt;Also, the larger a polyrepo project grows, the harder it becomes to migrate to a monorepo later. Personally, I prefer adopting a monorepo from the start.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting Up a Monorepo with pnpm Workspaces
&lt;/h1&gt;

&lt;p&gt;A monorepo can be set up using the workspace feature provided by package managers like npm, yarn, or pnpm. Package managers help treat each project as an independent package and make it easy to link them together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Workspace?
&lt;/h2&gt;

&lt;p&gt;A workspace allows you to manage multiple packages — each with their own &lt;code&gt;package.json&lt;/code&gt; — within a single repository. This makes it straightforward to connect packages inside a monorepo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring pnpm Workspaces
&lt;/h2&gt;

&lt;p&gt;Let me walk through setting up a monorepo from scratch using &lt;strong&gt;pnpm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, create a &lt;code&gt;pnpm-workspace.yaml&lt;/code&gt; file to define which packages are part of the workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;apps/*'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;packages/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;packages&lt;/code&gt; field specifies path patterns for packages included in the workspace.&lt;/li&gt;
&lt;li&gt;It's common to create &lt;code&gt;apps&lt;/code&gt; and &lt;code&gt;packages&lt;/code&gt; directories:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apps/&lt;/code&gt; — contains applications that will actually be deployed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages/&lt;/code&gt; — contains shared modules and libraries used across multiple apps.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;You're not required to use exactly &lt;code&gt;apps&lt;/code&gt; and &lt;code&gt;packages&lt;/code&gt; — feel free to structure it however makes sense for your project.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Linking Packages: Step 1 — Configure the Package
&lt;/h2&gt;

&lt;p&gt;Packages within a workspace can depend on each other. For example, let's say &lt;code&gt;apps/blog&lt;/code&gt; depends on &lt;code&gt;packages/ui&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, configure &lt;code&gt;packages/ui/package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;packages/ui/package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@repo/ui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Important!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Prevents&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;accidental&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;publishing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;npm&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/entry.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;point&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/entry.ts"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;definition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; field sets the package name — it must be unique within the workspace. Adding a scope like &lt;code&gt;@repo&lt;/code&gt; makes it clear that this package belongs to the monorepo.&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;private: true&lt;/code&gt; prevents the package from being accidentally published to npm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important: You need to explicitly expose the files that other packages will consume.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configure this just like any npm package — usually via &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;types&lt;/code&gt;, or &lt;code&gt;exports&lt;/code&gt; fields. One interesting detail in this example: we're exposing &lt;code&gt;.ts&lt;/code&gt; source files directly. This means the consuming app handles TypeScript compilation rather than the package itself, so there's no need to build each package separately — quite convenient.&lt;/p&gt;

&lt;p&gt;If the consuming app can't compile TypeScript from &lt;code&gt;node_modules&lt;/code&gt;, you can configure the package to expose compiled &lt;code&gt;.js&lt;/code&gt; files instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linking Packages: Step 2 — Add the Dependency
&lt;/h2&gt;

&lt;p&gt;Now let's add &lt;code&gt;@repo/ui&lt;/code&gt; as a dependency in &lt;code&gt;apps/blog&lt;/code&gt;. Add it to &lt;code&gt;dependencies&lt;/code&gt; or &lt;code&gt;devDependencies&lt;/code&gt;, then run &lt;code&gt;pnpm install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;apps/blog/package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;workspace:*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;workspace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;packages&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@repo/ui"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@repo/ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the monorepo package to add as a dependency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workspace:*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tells the package manager to resolve this from the local workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without &lt;code&gt;workspace:*&lt;/code&gt;, the package manager would attempt to find &lt;code&gt;@repo/ui&lt;/code&gt; on the npm registry rather than locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Package Manager Links Packages
&lt;/h2&gt;

&lt;p&gt;So how does pnpm actually connect workspace packages? In the JavaScript ecosystem, installed modules are stored in &lt;code&gt;node_modules&lt;/code&gt;, and bundlers resolve imports from there.&lt;/p&gt;

&lt;p&gt;Let's see how &lt;code&gt;@repo/ui&lt;/code&gt; shows up in &lt;code&gt;apps/blog/node_modules&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz2kgkknq5jd55ml205v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz2kgkknq5jd55ml205v.png" alt=" raw `@repo/ui` endraw  added to apps/blog's node_modules" width="472" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll find &lt;code&gt;@repo/ui&lt;/code&gt; in &lt;code&gt;apps/blog/node_modules&lt;/code&gt;, but with a &lt;code&gt;↳&lt;/code&gt; icon next to it — indicating it's a &lt;strong&gt;symbolic link&lt;/strong&gt;, not a physical copy of the files.&lt;/p&gt;

&lt;p&gt;This brings two key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disk space savings&lt;/strong&gt;: The package is linked rather than copied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time change propagation&lt;/strong&gt;: Any changes to the package source are immediately reflected without rebuilding.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using the Package
&lt;/h2&gt;

&lt;p&gt;Now you can import from &lt;code&gt;@repo/ui&lt;/code&gt; in &lt;code&gt;apps/blog&lt;/code&gt;:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;setupCounter&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;@repo/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setupCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLButtonElement&lt;/span&gt;&lt;span class="o"&gt;&amp;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;#counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you can import is determined by the &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;types&lt;/code&gt;, and &lt;code&gt;exports&lt;/code&gt; fields in &lt;code&gt;packages/ui/package.json&lt;/code&gt;. If an import fails, check these two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The package is listed in &lt;code&gt;dependencies&lt;/code&gt; or &lt;code&gt;devDependencies&lt;/code&gt; and &lt;code&gt;pnpm install&lt;/code&gt; has been run.&lt;/li&gt;
&lt;li&gt;The file you're trying to import is actually exposed in the package's &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full example is available in the &lt;a href="https://github.com/Malloc72P/mono-exam/tree/pnpm-workspace" rel="noopener noreferrer"&gt;pnpm monorepo example repo&lt;/a&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Turborepo
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Turborepo&lt;/strong&gt; is a monorepo management tool built by &lt;strong&gt;Vercel&lt;/strong&gt; (the team behind Next.js), and it's become one of the most popular tools in this space.&lt;/p&gt;

&lt;p&gt;Here's an important detail: Turborepo &lt;strong&gt;builds on top of&lt;/strong&gt; your package manager's workspace feature. So the pnpm workspace setup above stays essentially the same. So why use Turborepo?&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Turborepo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High-performance build system&lt;/td&gt;
&lt;td&gt;Uses change-based caching to only rebuild what changed, dramatically speeding up builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching &amp;amp; parallel execution&lt;/td&gt;
&lt;td&gt;Caches build artifacts and runs tasks in parallel to minimize build time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unified workflow&lt;/td&gt;
&lt;td&gt;Integrates tasks like testing, linting, and deployment into a consistent pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of Turborepo as filling in the gaps that pnpm workspaces leave behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Turborepo to Your Monorepo
&lt;/h2&gt;

&lt;p&gt;Starting from the pnpm workspace project, add &lt;code&gt;turbo&lt;/code&gt; as a dev dependency at the workspace root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# --workspace-root installs at the monorepo root level&lt;/span&gt;
pnpm add turbo &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--workspace-root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Defining a Build Pipeline with turbo.json
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;turbo.json&lt;/code&gt; file to configure the build pipeline — this defines and orchestrates tasks like builds, tests, and lints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://turborepo.dev/schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tasks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;upstream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;packages&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dependsOn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^build"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;these&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;invalidated&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;these&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;outputs&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".next/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"!.next/cache/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Schema for the turbo config file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tasks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines tasks runnable via the &lt;code&gt;turbo&lt;/code&gt; CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dependsOn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declares task dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls cache invalidation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies build artifacts to cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With &lt;code&gt;"dependsOn": ["^build"]&lt;/code&gt;, the &lt;code&gt;build&lt;/code&gt; task for any package will first run the &lt;code&gt;build&lt;/code&gt; tasks of its dependencies. In our project, since &lt;code&gt;apps/blog&lt;/code&gt; depends on &lt;code&gt;packages/ui&lt;/code&gt;, running &lt;code&gt;build&lt;/code&gt; for &lt;code&gt;apps/blog&lt;/code&gt; will automatically trigger &lt;code&gt;packages/ui&lt;/code&gt;'s build first.&lt;/p&gt;

&lt;p&gt;Turborepo manages build ordering based on your dependency graph automatically — no manual orchestration needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Turbo Commands
&lt;/h2&gt;

&lt;p&gt;Run the build pipeline from the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm turbo run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turbo will execute each package's build task in the correct order according to the configured pipeline.&lt;/p&gt;

&lt;p&gt;If that's too verbose, add a shorthand script to your root &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 json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turbo run build"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can simply run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The full Turborepo example is available in the &lt;a href="https://github.com/Malloc72P/mono-exam/tree/turbo-repo" rel="noopener noreferrer"&gt;Turborepo example repo&lt;/a&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Wrapping Up
&lt;/h1&gt;

&lt;p&gt;We've covered the concept of monorepos and how to set one up using pnpm workspaces and Turborepo. As a project scales, a monorepo really shows its value — boosting consistency and operational efficiency across your codebase. I'd encourage you to consider the monorepo approach for your next frontend project.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/frontend/monorepo" rel="noopener noreferrer"&gt;Read the full post on my blog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>turborepo</category>
      <category>pnpm</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How JavaScript Really Executes Code: Execution Context and Scope Chain Explained</title>
      <dc:creator>Malloc72P</dc:creator>
      <pubDate>Fri, 10 Apr 2026 00:15:25 +0000</pubDate>
      <link>https://dev.to/malloc72p/how-javascript-really-executes-code-execution-context-and-scope-chain-explained-4c9f</link>
      <guid>https://dev.to/malloc72p/how-javascript-really-executes-code-execution-context-and-scope-chain-explained-4c9f</guid>
      <description>&lt;p&gt;Note: This post is a translated version of an article originally published on my personal blog. You can read the &lt;a href="https://blog.malloc72p.com/posts/frontend/execution-context" rel="noopener noreferrer"&gt;original Korean post here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; This post is written based on the ES5 specification. In ES6+, some details changed — such as where &lt;code&gt;ThisBinding&lt;/code&gt; lives, and the distinct roles of &lt;code&gt;VariableEnvironment&lt;/code&gt; vs &lt;code&gt;LexicalEnvironment&lt;/code&gt;. I'll cover the ES6 version in a follow-up post.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Should You Care About Execution Context?
&lt;/h2&gt;

&lt;p&gt;If you've been writing JavaScript for a while, you've probably run into these puzzling moments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same &lt;code&gt;this&lt;/code&gt; keyword points to completely different objects depending on the situation&lt;/li&gt;
&lt;li&gt;A variable you never declared somehow doesn't throw an error&lt;/li&gt;
&lt;li&gt;You can freely access a variable that's defined &lt;em&gt;outside&lt;/em&gt; your current scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sure, you can get by without knowing why these things happen. But eventually you hit a wall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your code "just works" but you can't explain why&lt;/li&gt;
&lt;li&gt;You can't control what &lt;code&gt;this&lt;/code&gt; points to&lt;/li&gt;
&lt;li&gt;Variables get shared in unintended ways, spawning bugs everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding &lt;strong&gt;execution context&lt;/strong&gt; is the key to breaking through that wall.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is an Execution Context?
&lt;/h2&gt;

&lt;p&gt;An execution context is an &lt;strong&gt;object that holds all the environmental information needed to execute a piece of code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;JavaScript might look like it runs line by line, but under the hood, the engine first collects and prepares environmental information &lt;em&gt;before&lt;/em&gt; executing anything. That prepared environment is the execution context.&lt;/p&gt;

&lt;p&gt;When an execution context is activated, it gathers three pieces of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VariableEnvironment (VE)&lt;/strong&gt; — a snapshot of the initial environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LexicalEnvironment (LE)&lt;/strong&gt; — the live environment that reflects changes during execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ThisBinding&lt;/strong&gt; — what &lt;code&gt;this&lt;/code&gt; refers to in the current context&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Are Execution Contexts Created?
&lt;/h2&gt;

&lt;p&gt;Execution contexts are created at two points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When JavaScript first runs&lt;/strong&gt; → the &lt;strong&gt;Global Execution Context&lt;/strong&gt; is created&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When a function is called&lt;/strong&gt; → a &lt;strong&gt;Function Execution Context&lt;/strong&gt; is created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The global execution context is born when the script starts and lives until the script ends.&lt;br&gt;
A function execution context is created fresh every time a function is called, and it's discarded once the function finishes.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Call Stack and Execution Contexts
&lt;/h2&gt;

&lt;p&gt;Execution contexts are managed through the &lt;strong&gt;Call Stack&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;second&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;second&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how this plays out step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The global execution context is pushed onto the call stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first()&lt;/code&gt; is called → its execution context is pushed on top&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;second()&lt;/code&gt; is called → its execution context is pushed on top&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;second&lt;/code&gt; finishes → its context is popped off&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first&lt;/code&gt; finishes → its context is popped off&lt;/li&gt;
&lt;li&gt;The global context is removed when the script ends&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The context at the &lt;strong&gt;top of the stack&lt;/strong&gt; is the one currently running. When a new function is called, the current context pauses and the new one takes over.&lt;/p&gt;




&lt;h1&gt;
  
  
  VariableEnvironment vs. LexicalEnvironment
&lt;/h1&gt;

&lt;p&gt;Both environments start out identical when an execution context is created. Over time, they diverge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LexicalEnvironment&lt;/strong&gt; — updated in real time as code executes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VariableEnvironment&lt;/strong&gt; — frozen at the state it was in when the context was first created&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  LexicalEnvironment
&lt;/h1&gt;

&lt;p&gt;The LexicalEnvironment has two components.&lt;/p&gt;

&lt;h2&gt;
  
  
  environmentRecord
&lt;/h2&gt;

&lt;p&gt;This is where &lt;strong&gt;identifier information&lt;/strong&gt; for the current execution context is stored — parameter names, function declarations, and variable names all live here.&lt;/p&gt;

&lt;p&gt;Before executing any code, the JavaScript engine scans through the scope and builds up this &lt;code&gt;environmentRecord&lt;/code&gt;. It collects all the variable names ahead of time.&lt;/p&gt;

&lt;p&gt;This is what we call &lt;strong&gt;hoisting&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a deeper look at hoisting, check out my &lt;a href="https://blog.malloc72p.com/posts/frontend/hoisting" rel="noopener noreferrer"&gt;hoisting post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The global execution context works slightly differently: its &lt;code&gt;environmentRecord&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; the global object (&lt;code&gt;window&lt;/code&gt; in browsers, &lt;code&gt;global&lt;/code&gt; in Node.js). This is exactly why variables declared with &lt;code&gt;var&lt;/code&gt; at the top level are accessible via &lt;code&gt;window.yourVariable&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  outerEnvironmentReference
&lt;/h2&gt;

&lt;p&gt;This is a reference to the LexicalEnvironment of the &lt;strong&gt;scope in which the current function was declared&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The word "declared" is key here. It doesn't matter where the function is &lt;em&gt;called&lt;/em&gt; — only where it was &lt;em&gt;written&lt;/em&gt;. This is what makes JavaScript &lt;strong&gt;lexically scoped&lt;/strong&gt;, and it's also the core principle behind closures.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;code&gt;outerEnvironmentReference&lt;/code&gt;, when the engine needs to look up a variable, it can start in the current scope and walk outward.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Scope Chain
&lt;/h1&gt;

&lt;h2&gt;
  
  
  How Variables Are Looked Up
&lt;/h2&gt;

&lt;p&gt;When the JavaScript engine needs to resolve a variable, it searches in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look in the current context's &lt;code&gt;environmentRecord&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If not found, follow &lt;code&gt;outerEnvironmentReference&lt;/code&gt; to the outer scope&lt;/li&gt;
&lt;li&gt;Repeat this process all the way up to the global scope&lt;/li&gt;
&lt;li&gt;If still not found → &lt;code&gt;ReferenceError&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This chain of scopes — where the engine climbs outward looking for a declaration — is called the &lt;strong&gt;scope chain&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope Chain in Action
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// No `a` in inner's scope&lt;/span&gt;
    &lt;span class="c1"&gt;// → follows outerEnvironmentReference to outer's scope&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inner &amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outer &amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;();&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;global &amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outer &amp;gt;&amp;gt;&amp;gt;  outer
inner &amp;gt;&amp;gt;&amp;gt;  outer
global &amp;gt;&amp;gt;&amp;gt;  global
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though &lt;code&gt;inner&lt;/code&gt; never declares &lt;code&gt;a&lt;/code&gt;, it finds &lt;code&gt;outer&lt;/code&gt;'s &lt;code&gt;a&lt;/code&gt; through the scope chain.&lt;/p&gt;

&lt;p&gt;Notice the last line: &lt;code&gt;global &amp;gt;&amp;gt;&amp;gt; global&lt;/code&gt;. The &lt;code&gt;var a = 'outer'&lt;/code&gt; inside &lt;code&gt;outer&lt;/code&gt; is a completely separate variable from the global &lt;code&gt;a&lt;/code&gt;. &lt;code&gt;outer&lt;/code&gt; declared its own &lt;code&gt;a&lt;/code&gt; in its own scope, so the global &lt;code&gt;a&lt;/code&gt; stays untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope Is One-Way: Inside-Out Only
&lt;/h2&gt;

&lt;p&gt;An important property of the scope chain is that it's &lt;strong&gt;unidirectional&lt;/strong&gt; — inner scopes can see outer scopes, but not the other way around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;outerVar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;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;outerVar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError: outerVar is not defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying to access &lt;code&gt;outerVar&lt;/code&gt; from outside the function throws an error because the global scope has no visibility into &lt;code&gt;outer&lt;/code&gt;'s internals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An execution context is an object holding the environmental info (VE, LE, ThisBinding) needed to run code.&lt;/li&gt;
&lt;li&gt;A new execution context is created every time a function is called; it gets pushed onto the call stack and popped off when done.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;environmentRecord&lt;/code&gt;&lt;/strong&gt; in LexicalEnvironment stores identifier info for the current scope.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;code&gt;outerEnvironmentReference&lt;/code&gt;&lt;/strong&gt; points to the outer environment &lt;em&gt;where the function was declared&lt;/em&gt;, not where it was called.&lt;/li&gt;
&lt;li&gt;Variable lookup starts in the current scope and walks outward through &lt;code&gt;outerEnvironmentReference&lt;/code&gt; — this is the scope chain.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Execution context is the foundation that ties together some of JavaScript's most important behaviors: hoisting, scope, and &lt;code&gt;this&lt;/code&gt; binding all connect back to it.&lt;/p&gt;

&lt;p&gt;In particular, the scope chain formed through &lt;code&gt;outerEnvironmentReference&lt;/code&gt; is also the core mechanism behind &lt;a href="https://blog.malloc72p.com/posts/frontend/closure" rel="noopener noreferrer"&gt;closures&lt;/a&gt; — which I'll explore in a future post.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/frontend/execution-context" rel="noopener noreferrer"&gt;Read the full post on my blog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How Can We Stream AI Chat Messages Like ChatGPT?</title>
      <dc:creator>Malloc72P</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:19:13 +0000</pubDate>
      <link>https://dev.to/malloc72p/how-can-we-stream-ai-chat-messages-like-chatgpt-3dfi</link>
      <guid>https://dev.to/malloc72p/how-can-we-stream-ai-chat-messages-like-chatgpt-3dfi</guid>
      <description>&lt;p&gt;Note: This post is a translated version of an article originally published on my personal blog. You can read the &lt;a href="https://blog.malloc72p.com/posts/ai/sse-exam" rel="noopener noreferrer"&gt;original Korean post here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can We Stream AI Chat Messages Like ChatGPT?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can We Do It With Traditional HTTP?
&lt;/h3&gt;

&lt;p&gt;When using services like ChatGPT, Claude, or Gemini, you'll notice that the AI's response is printed out on the screen bit by bit. How exactly is this implemented?&lt;br&gt;&lt;br&gt;
Fundamentally, web services operate on the HTTP protocol. This protocol works as unidirectional communication: the client sends a request to the server, and the server sends back a single response.&lt;br&gt;&lt;br&gt;
However, what we want is for the server to send AI message tokens down to the client as soon as they are ready. The traditional request-response pair we are familiar with isn't quite cut out for this.&lt;/p&gt;
&lt;h3&gt;
  
  
  Can We Use WebSockets?
&lt;/h3&gt;

&lt;p&gt;As an alternative, we could use WebSockets, but this isn't a great approach either. Here's why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming messages doesn't actually require a bidirectional channel. We only need the server to be able to respond in multiple chunks after the client makes a single request.&lt;/li&gt;
&lt;li&gt;Since it's not HTTP, we can't leverage existing HTTP features out of the box, such as authentication (cookies, tokens), CORS, caching, and logging.&lt;/li&gt;
&lt;li&gt;It makes scaling difficult.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Load balancer configuration is the biggest headache. In a typical web service, a load balancer distributes traffic across multiple servers. But if you use WebSockets, a specific server must maintain a persistent connection with the client. Therefore, you have to keep the connection alive using Sticky Sessions, which makes horizontal scaling difficult because traffic won't be distributed evenly.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Answer is Server-Sent Events (SSE)
&lt;/h3&gt;

&lt;p&gt;Actually, this problem can be solved with the HTTP protocol. A typical HTTP response sends the entire payload at once, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Content-Length: 42
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because there is a &lt;code&gt;Content-Length&lt;/code&gt;, the browser closes the connection once it receives that amount of data.&lt;/p&gt;

&lt;p&gt;On the other hand, an SSE response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/event-stream

data: chunk 1

data: chunk 2

...

data: chunk N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that there is no &lt;code&gt;Content-Length&lt;/code&gt;, and &lt;code&gt;Transfer-Encoding&lt;/code&gt; is set to &lt;code&gt;chunked&lt;/code&gt;. In this case, the browser assumes the response is not yet finished, keeps the connection open, and processes the data whenever it arrives.&lt;br&gt;&lt;br&gt;
Just by doing this, we can maintain the standard HTTP protocol while allowing the server to send a response in multiple chunks for a single request. This is how we can stream AI messages.&lt;br&gt;&lt;br&gt;
Simpler than you thought, right?&lt;/p&gt;

&lt;p&gt;Well, seeing is believing, so let's implement it ourselves.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing Message Streaming with SSE
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In this tutorial, we will create a Next.js app and implement a Route Handler that responds with messages using SSE. First, let's setup the Next.js app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Next.js App Setup
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a next app using boilerplate&lt;/span&gt;
pnpm create next-app@16.2.1 sse-exam &lt;span class="nt"&gt;--yes&lt;/span&gt;

&lt;span class="c"&gt;# Navigate to the generated project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;sse-exam

&lt;span class="c"&gt;# Run the dev server&lt;/span&gt;
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;We're all set. Now let's create the Route Handler.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Creating the Route Handler 1: Preparation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create the &lt;code&gt;app/api/route.ts&lt;/code&gt; file and paste the following code:&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dummy tokens for testing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Never gonna give you up &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;Never gonna let you down &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;Never gonna run around and desert you &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;Never gonna make you cry &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;Never gonna say goodbye &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;Never gonna tell a lie and hurt you &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="cm"&gt;/**
 * A function to simulate an external API call by delaying execution.
 * @param ms Delay time in milliseconds
 */&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;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="nf"&gt;setTimeout&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;ms&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;ul&gt;
&lt;li&gt;
&lt;code&gt;tokens&lt;/code&gt; is a list of dummy tokens we'll use for testing. We need some data for the server to respond with!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sleep&lt;/code&gt; is used to simulate the latency of calling an external API. In reality, calling the GPT or Anthropic API introduces latency, so we use this to mimic that behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Creating the Route Handler 2: GET Request Handler
&lt;/h3&gt;

&lt;p&gt;Now let's implement the handler for the GET request. Add the following function to the &lt;code&gt;app/api/route.ts&lt;/code&gt; file:&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;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;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a ReadableStream for SSE&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&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;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Encode tokens and enqueue them into the controller&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Format the data according to the SSE standard&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encodedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&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;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: [DONE]&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;// Create and return a Response object containing the Stream&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&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;Cache-Control&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;no-cache&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might look complicated, but it's actually quite simple. You just create a &lt;code&gt;ReadableStream&lt;/code&gt; object, put it inside a &lt;code&gt;Response&lt;/code&gt; object, and return it.&lt;br&gt;&lt;br&gt;
Let's dig a little deeper.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deep Dive into ReadableStream
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ReadableStream&lt;/code&gt; is an object that reads and transmits data in chunks, rather than sending the entire payload at once.&lt;/li&gt;
&lt;li&gt;You can provide an asynchronous &lt;code&gt;start&lt;/code&gt; function as an argument to the constructor, where you can write the logic for reading and sending data in chunks.&lt;/li&gt;
&lt;li&gt;To read and send a chunk, simply call &lt;code&gt;enqueue&lt;/code&gt; on the &lt;code&gt;controller&lt;/code&gt; parameter of the &lt;code&gt;start&lt;/code&gt; callback.&lt;/li&gt;
&lt;li&gt;Once all transmissions are complete, you can call &lt;code&gt;close&lt;/code&gt; to end the stream.&lt;/li&gt;
&lt;li&gt;A simplified version looks like this:&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Called once when the stream is created&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first chunk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Push data&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;second chunk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Close stream&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;h3&gt;
  
  
  Deep Dive into Response
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create and return a Response object containing the Stream&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream&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;Cache-Control&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;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;When creating the response object, we define a few attributes in the headers.&lt;/li&gt;
&lt;li&gt;As mentioned earlier, we set &lt;code&gt;Content-Type&lt;/code&gt; to &lt;code&gt;text/event-stream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Also, since we passed a &lt;code&gt;ReadableStream&lt;/code&gt; as the body of the &lt;code&gt;Response&lt;/code&gt;, the &lt;code&gt;transfer-encoding&lt;/code&gt; is automatically set to &lt;code&gt;chunked&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The client calling this API will read these attributes from the HTTP response headers and realize, "Ah, the server is sending the message in chunks. I should treat the result as a &lt;code&gt;ReadableStream&lt;/code&gt; and read it accordingly."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cache-Control&lt;/code&gt; isn't strictly required, but it's added defensively to prevent unexpected caching in production environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Midpoint Summary
&lt;/h3&gt;

&lt;p&gt;That was a long explanation, so let's summarize before moving on.&lt;/p&gt;

&lt;p&gt;First, the advantages and characteristics of SSE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To stream fragmented AI messages down to the client, you should use the SSE approach.&lt;/li&gt;
&lt;li&gt;Because SSE operates over the standard HTTP protocol, you can use existing authentication methods (cookies, tokens) as is.&lt;/li&gt;
&lt;li&gt;Unlike WebSockets, it remains unidirectional, making horizontal scaling much easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to implement SSE on the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;ReadableStream&lt;/code&gt; object and place it in the body of a &lt;code&gt;Response&lt;/code&gt; object to send it back.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;Content-Type&lt;/code&gt; header to &lt;code&gt;text/event-stream&lt;/code&gt;. By placing a stream object in the body, the &lt;code&gt;transfer-encoding&lt;/code&gt; automatically becomes &lt;code&gt;chunked&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Client-Side Implementation
&lt;/h3&gt;

&lt;p&gt;Now let's see how the client calls the API that sends messages via SSE.&lt;br&gt;&lt;br&gt;
First, open &lt;code&gt;app/page.tsx&lt;/code&gt; and modify it as follows:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;useState&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="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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&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;onClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="c1"&gt;// Write the API call logic here!&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;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-160 h-175 bg-gray-50 border border-gray-300 shadow-md rounded-md mx-auto mt-10 overflow-hidden"&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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-between items-center bg-gray-100 p-5 border-b border-b-gray-300"&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="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"font-bold text-xl"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;SSE Exam&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;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-2 bg-blue-700 rounded-md text-white font-bold hover:brightness-105 cursor-pointer"&lt;/span&gt;
          &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Request AI
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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="nt"&gt;div&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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-5 overflow-auto"&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-500 text-2xl"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;no data!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;message&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;&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is a simple UI that displays the message sent from the server via SSE when the &lt;code&gt;Request AI&lt;/code&gt; button is clicked.&lt;/li&gt;
&lt;li&gt;Now let's implement &lt;code&gt;onClick&lt;/code&gt;. Write it like this:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&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;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid response. Failed to get reader.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;while &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="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;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&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;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data: [DONE]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&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;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&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;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;Since the server sent a &lt;code&gt;ReadableStream&lt;/code&gt; in the body, the client must also read it as a &lt;code&gt;ReadableStream&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
You can easily extract the stream reader object by calling &lt;code&gt;response.body.getReader()&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
We run a loop reading messages until there's no more data left (&lt;code&gt;done&lt;/code&gt;).&lt;br&gt;
At this point, you might worry that entering a &lt;code&gt;while(true)&lt;/code&gt; loop will block the UI thread, but it's perfectly fine.&lt;br&gt;
If you look closely, there is an &lt;code&gt;await&lt;/code&gt; on &lt;code&gt;reader.read()&lt;/code&gt;. Because this method is asynchronous, the event loop will process other tasks until the server sends a chunk.&lt;br&gt;
Therefore, this code will not block the UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking Network Requests in DevTools
&lt;/h3&gt;

&lt;p&gt;Now, when you press the &lt;code&gt;Request AI&lt;/code&gt; button, you'll see the message being printed out piece by piece on the screen.&lt;br&gt;
To see how the transmission actually happens, let's open the Network tab in DevTools.&lt;/p&gt;

&lt;p&gt;First, here are the response message headers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cache-control&lt;/td&gt;
&lt;td&gt;no-cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;content-type&lt;/td&gt;
&lt;td&gt;text/event-stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transfer-encoding&lt;/td&gt;
&lt;td&gt;chunked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;As configured earlier, you can see that the content type and transfer encoding are set correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, here is the list of messages actually received in the EventStream tab:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna give you up "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:21.967&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna let you down "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:22.179&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna run around and desert you "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:22.280&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna make you cry "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:22.380&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna say goodbye "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:22.482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"text":"Never gonna tell a lie and hurt you "}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;11:04:22.583&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The EventStream tab is specific to SSE transmissions, showing exactly which messages were sent in chronological order.&lt;br&gt;
It's truly fascinating that you can respond in chunks as soon as the data is ready using simple HTTP configurations and &lt;code&gt;ReadableStream&lt;/code&gt;, without any complex WebSocket setup.&lt;br&gt;&lt;br&gt;
Honestly, before researching how this feature was implemented, I naturally assumed it used WebSockets. Finding out that it can be done over HTTP was a pleasant surprise.&lt;br&gt;
Even outside of AI chatbots, HTTP chunked transfer—which underpins SSE—can be used when transmitting large files bit by bit. I read that combining this with the MediaSource API allows browsers to receive and play video or audio chunks seamlessly.&lt;/p&gt;

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

&lt;p&gt;In this post, we explored how to leverage HTTP specifications to stream AI messages to the client, much like ChatGPT or Claude.&lt;br&gt;
Before looking into it, I thought it would be difficult, assuming WebSockets were mandatory. However, I was surprised by how easy and intuitive it turned out to be.&lt;br&gt;
I definitely want to use this approach when building my next AI-powered project.&lt;/p&gt;

&lt;p&gt;Check out the complete source code for this tutorial in the &lt;a href="https://github.com/Malloc72P/sse-exam" rel="noopener noreferrer"&gt;sse-exam repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading this post!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Full-Stack Data Grid App with Next.js, Prisma, and AG Grid</title>
      <dc:creator>Malloc72P</dc:creator>
      <pubDate>Thu, 26 Mar 2026 06:56:18 +0000</pubDate>
      <link>https://dev.to/malloc72p/building-a-full-stack-data-grid-app-with-nextjs-prisma-and-ag-grid-1abg</link>
      <guid>https://dev.to/malloc72p/building-a-full-stack-data-grid-app-with-nextjs-prisma-and-ag-grid-1abg</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build a complete CRUD data grid app using &lt;strong&gt;Next.js&lt;/strong&gt; (Server Components + Server Actions), &lt;strong&gt;Prisma ORM&lt;/strong&gt; (type-safe DB access), and &lt;strong&gt;AG Grid&lt;/strong&gt; (inline editing, sorting, virtual scroll).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server Components&lt;/strong&gt; fetch data on the server; &lt;strong&gt;Server Actions&lt;/strong&gt; let clients call server-side functions like regular async functions — no REST API needed.&lt;/li&gt;
&lt;li&gt;Prisma provides &lt;strong&gt;auto-generated TypeScript types&lt;/strong&gt; from your schema, making DB queries fully type-safe.&lt;/li&gt;
&lt;li&gt;AG Grid's &lt;code&gt;applyTransaction&lt;/code&gt; API enables &lt;strong&gt;optimistic updates&lt;/strong&gt; — the UI updates instantly, and rolls back on failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Next.js: Server Components &amp;amp; Server Actions
&lt;/h3&gt;

&lt;p&gt;Server Components run on the server and can call business logic directly:&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;// app/page.tsx — Server Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;Home&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;orders&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;getOrders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Direct DB call&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;OrderGrid&lt;/span&gt; &lt;span class="na"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orders&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;Server Actions let client components call server functions with a simple &lt;code&gt;'use server'&lt;/code&gt; directive:&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="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="p"&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;createOrderAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateOrderInput&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="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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 client calls it like a normal function — Next.js handles the HTTP layer internally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prisma ORM: Type-Safe Database Access
&lt;/h3&gt;

&lt;p&gt;Define your schema in Prisma's DSL, then generate a fully typed client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Order {
  id    Int @id @default(autoincrement())
  price Int
  qty   Int
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CRUD operations are concise and type-safe:&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;// Create&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Read with filters&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Update&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;qty&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Delete&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AG Grid: Optimistic Updates with applyTransaction
&lt;/h3&gt;

&lt;p&gt;The optimistic update pattern — update the UI first, then sync with the server:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCreate&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;tempId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tempRow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tempId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Optimistic: add temp row immediately&lt;/span&gt;
  &lt;span class="nx"&gt;gridRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tempRow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;try&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;created&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;createOrderAction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// Replace temp row with real data&lt;/span&gt;
    &lt;span class="nx"&gt;gridRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tempRow&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;created&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Rollback on failure&lt;/span&gt;
    &lt;span class="nx"&gt;gridRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tempRow&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;The same pattern applies to update (rollback cell value on failure) and delete (re-add the row on failure).&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;p&gt;Setting up the AG Grid component with inline editing and a delete button:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colDefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColDef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;editable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asc&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;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;editable&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;editable&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cellRenderer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ICellRendererParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!&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;gt;&lt;/span&gt;Delete&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgGridReact&lt;/span&gt;
  &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;gridRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;columnDefs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colDefs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;getRowId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onCellValueChanged&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleCellValueChanged&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;This is a summary of my original 3-part series written in Korean.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;For the full articles with step-by-step setup instructions, check out the originals:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/fullstack/nextjs-grid-app-1" rel="noopener noreferrer"&gt;Part 1: Next.js Basics&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/fullstack/nextjs-grid-app-2" rel="noopener noreferrer"&gt;Part 2: Prisma ORM Setup &amp;amp; CRUD&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/fullstack/nextjs-grid-app-3" rel="noopener noreferrer"&gt;Part 3: AG Grid &amp;amp; Optimistic Updates&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>prisma</category>
      <category>react</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>AntiGravity: Getting the Most Out of Agentic Coding with Rules, Skills, and Workflows</title>
      <dc:creator>Malloc72P</dc:creator>
      <pubDate>Thu, 26 Mar 2026 06:56:08 +0000</pubDate>
      <link>https://dev.to/malloc72p/antigravity-getting-the-most-out-of-agentic-coding-with-rules-skills-and-workflows-54pb</link>
      <guid>https://dev.to/malloc72p/antigravity-getting-the-most-out-of-agentic-coding-with-rules-skills-and-workflows-54pb</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AntiGravity&lt;/strong&gt; is a VS Code fork by Google DeepMind, built for agentic coding with enhanced AI agent features.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.agent&lt;/code&gt; folder lets you define &lt;strong&gt;Rules&lt;/strong&gt;, &lt;strong&gt;Skills&lt;/strong&gt;, and &lt;strong&gt;Workflows&lt;/strong&gt; to give your AI agent project-specific context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt; set coding conventions the agent must follow (e.g., architecture, style guides). Use &lt;code&gt;always_on&lt;/code&gt; sparingly — prefer &lt;code&gt;model_decision&lt;/code&gt; to avoid context overload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; teach the agent &lt;em&gt;how&lt;/em&gt; to solve specific problems using specific tools (e.g., using GitHub MCP to fetch commit history).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflows&lt;/strong&gt; automate repetitive command sequences (e.g., install → type-check → build) and can be triggered via slash commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rules: Coding Standards for Your Agent
&lt;/h3&gt;

&lt;p&gt;Rules live in &lt;code&gt;.agent/rules/&lt;/code&gt; and define principles the agent must follow. Each rule file uses frontmatter to control when it activates:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always_on&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Always-on rules (use sparingly)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model_decision&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Backend rules for Server Actions, Prisma, Auth, etc.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Contextual rules — agent decides when to apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep rules concise. If the agent wouldn't make a mistake without the rule, leave it out — too much context causes hallucinations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills: Teaching Your Agent How to Solve Problems
&lt;/h3&gt;

&lt;p&gt;Skills (&lt;code&gt;.agent/skills/&lt;/code&gt;) define &lt;em&gt;what tool to use&lt;/em&gt; and &lt;em&gt;how to use it&lt;/em&gt; for specific scenarios.&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git-history&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Uses GitHub MCP to fetch and summarize recent commit history.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## When to Use&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; "What changed recently?"
&lt;span class="p"&gt;-&lt;/span&gt; "Summarize recent commits"

&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Identify repo owner/name
&lt;span class="p"&gt;2.&lt;/span&gt; Call &lt;span class="sb"&gt;`list_commits`&lt;/span&gt; via GitHub MCP (perPage: 5-10)
&lt;span class="p"&gt;3.&lt;/span&gt; Optionally call &lt;span class="sb"&gt;`get_commit`&lt;/span&gt; with &lt;span class="sb"&gt;`include_diff: true`&lt;/span&gt;
&lt;span class="p"&gt;4.&lt;/span&gt; Summarize changes in categories (features, fixes, refactors)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Workflows: Automating Repetitive Tasks
&lt;/h3&gt;

&lt;p&gt;Workflows (&lt;code&gt;.agent/workflows/&lt;/code&gt;) chain commands together:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; type-check workflow&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;1.&lt;/span&gt; Install dependencies
// turbo
'pnpm install'
&lt;span class="p"&gt;
2.&lt;/span&gt; Type check
// turbo
'pnpm check-types'
&lt;span class="p"&gt;
3.&lt;/span&gt; Build
// turbo
'pnpm build'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;// turbo&lt;/code&gt; comment tells the agent to run the command automatically without user approval.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is a summary of my original post written in Korean.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;For the full article with detailed explanations and more examples, check out the original:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://blog.malloc72p.com/posts/ai/antigravity" rel="noopener noreferrer"&gt;Read the full post on my blog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agenticcoding</category>
      <category>vscode</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
