<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <feedpress:newsletterId>telerik-blogs-web</feedpress:newsletterId>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs--web-5ab52bef0a72f.jpg</logo>
  <title type="text">Telerik Blogs | Web</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:0e2d0697-8f65-4fda-a8c5-fc801a344fab;id=2217</id>
  <updated>2026-05-10T18:36:22Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/web"/>
  <entry>
    <id>urn:uuid:5b92bc97-ce93-4caf-954e-4312b10c6ede</id>
    <title type="text">How to Build Semantic Search for Documentation with NestJS, Qdrant and Xenova</title>
    <summary type="text">In this post, we’ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords.</summary>
    <published>2026-05-08T13:01:01Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17336853/how-build-semantic-search-documentation-nestjs-qdrant-xenova"/>
    <content type="text"><![CDATA[<p><span class="featured">In this post, we&rsquo;ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords. Let&rsquo;s get started!</span></p><p>In this post, we&rsquo;ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords. We&rsquo;ll use <a target="_blank" href="https://qdrant.tech/">Qdrant</a> as the vector database, <a target="_blank" href="https://www.npmjs.com/package/@xenova/transformers">Xenova/transformers</a> to generate local text embeddings and <a target="_blank" href="https://nestjs.com/">NestJS</a> as our API to tie everything together.</p><p>We will learn how to run Qdrant with <a target="_blank" href="https://www.docker.com/">Docker</a>, generate embeddings in <a target="_blank" href="https://nodejs.org/en">Node.js</a> and index docs as vectors with metadata in Qdrant. Our documentation API will provide a pure semantic search endpoint and a hybrid search endpoint that combines filters for an even more effective search.</p><h2 id="prerequisites">Prerequisites</h2><ul><li>Basic knowledge of NestJS and <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a> </li><li>Basic knowledge of HTTP, RESTful APIs, and cURL</li><li>Node.js and Docker should be installed</li></ul><h2 id="how-semantic-search-works">How Semantic Search Works</h2><p>Semantic search focuses on meaning, not just words. It understands user intent and contextual meaning, then finds data with similar meaning rather than matching keywords. Semantic search solves this by converting text into vectors (arrays of numbers) that capture meaning, and then comparing these vectors to find related information.</p><p>For example, if our docs contain the phrase &ldquo;How to authenticate users using JWT&rdquo; and a user searches for &ldquo;login security setup,&rdquo; semantic search can infer they mean the same thing.</p><h2 id="what-is-qdrant">What Is Qdrant?</h2><p>Qdrant is a vector database built for speed. It stores vectors and handles nearest neighbor calculations quickly. Qdrant uses the HNSW algorithm (Hierarchical Navigable Small World) to find similar vectors and return results in milliseconds. We&rsquo;ll use the official Docker image to run it locally, which keeps our environment clean and makes the database easy to start and stop.</p><h2 id="what-is-xenova">What Is Xenova?</h2><p>Xenova lets you run machine learning models directly in Node.js. We&rsquo;ll use it through the <code>@xenova/transformers</code> package to generate embeddings locally. This means no API calls, no rate limits and our data doesn&rsquo;t leave our machine. The model downloads once (~23 MB) and caches locally for future use.</p><h2 id="project-setup">Project Setup</h2><p>First, create a NestJS project:</p><pre class=" language-shell"><code class="prism  language-shell">nest new semantic-search-api
cd semantic-search-api
</code></pre><p>Next, run the command below to install our dependencies:</p><pre class=" language-shell"><code class="prism  language-shell">npm install @nestjs/config @qdrant/js-client-rest @xenova/transformers uuid \
  &amp;&amp; npm install --save-dev @types/uuid
</code></pre><p>In our <code>install command</code>, <code>@nestjs/config</code> is used to import environment variables into our app, <code>@qdrant/js-client-rest</code> is the JavaScript client for interacting with the Qdrant vector database, <code>@xenova/transformers</code> is used to generate local text embeddings, and <code>uuid</code> is used to create unique identifiers for documents and embeddings.</p><h2 id="running-qdrant-with-docker">Running Qdrant with Docker</h2><p>Instead of installing Qdrant directly, we&rsquo;ll use Docker Compose to keep our environment clean. Create a <code>docker-compose.yml</code> file at the root of your project and paste the code below:</p><pre class=" language-yml"><code class="prism  language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3.8'</span>

<span class="token key atrule">services</span><span class="token punctuation">:</span>
  <span class="token key atrule">qdrant</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> qdrant/qdrant<span class="token punctuation">:</span>latest
    <span class="token key atrule">container_name</span><span class="token punctuation">:</span> qdrant
    <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped
    <span class="token key atrule">ports</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> "6333<span class="token punctuation">:</span>6333"  <span class="token comment"># REST API port</span>
    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> ./qdrant_storage<span class="token punctuation">:</span>/qdrant/storage
</code></pre><p>Start the database in the background:</p><pre class=" language-shell"><code class="prism  language-shell">docker-compose up -d
</code></pre><p>Next, create a <code>.env</code> file and paste your Qdrant connection settings and embedding configuration:</p><pre class=" language-js"><code class="prism  language-js">QDRANT_URL<span class="token operator">=</span>http<span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>localhost<span class="token punctuation">:</span><span class="token number">6333</span>
QDRANT_COLLECTION<span class="token operator">=</span>documentation
QDRANT_VECTOR_DIMENSION<span class="token operator">=</span><span class="token number">384</span>
HF_MODEL_CACHE<span class="token operator">=</span><span class="token punctuation">.</span><span class="token operator">/</span>models
</code></pre><p>The variables above configure Qdrant&rsquo;s URL and collection name, set the vector dimension to 384 (which matches our embedding model), and specify where Xenova caches the downloaded model.</p><p>Next, let&rsquo;s update the <code>app</code> module to import the <code>ConfigModule</code>, so that we can load environment variables in our app:</p><p>Update your <code>app.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    ConfigModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      isGlobal<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      envFilePath<span class="token punctuation">:</span> <span class="token string">'.env'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">// ... we will add other modules here later</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="project-structure">Project Structure</h2><p>Our project structure will look like this:</p><pre class=" language-js"><code class="prism  language-js">src<span class="token operator">/</span>
├── qdrant<span class="token operator">/</span>
│   ├── qdrant<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── qdrant<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── embeddings<span class="token operator">/</span>
│   ├── embeddings<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── embeddings<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── documents<span class="token operator">/</span>
│   ├── documents<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   ├── documents<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
│   ├── document<span class="token operator">-</span>ingestion<span class="token operator">/</span>
│   │   ├── document<span class="token operator">-</span>ingestion<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│   │   └── document<span class="token operator">-</span>ingestion<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
│   └── document<span class="token operator">-</span>processor<span class="token operator">/</span>
│       ├── document<span class="token operator">-</span>processor<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│       └── document<span class="token operator">-</span>processor<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
└── search<span class="token operator">/</span>
    ├── search<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
    ├── search<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
    ├── search<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
    └── search<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
</code></pre><p>Run the command below to generate the necessary files:</p><pre class=" language-shell"><code class="prism  language-shell">nest g module qdrant &amp;&amp; \
nest g service qdrant &amp;&amp; \
nest g module embeddings &amp;&amp; \
nest g service embeddings &amp;&amp; \
nest g module documents &amp;&amp; \
nest g service documents/document-processor &amp;&amp; \
nest g service documents/document-ingestion &amp;&amp; \
nest g controller documents &amp;&amp; \
nest g module search &amp;&amp; \
nest g service search &amp;&amp; \
nest g controller search
</code></pre><h2 id="choosing-an-embedding-model">Choosing an Embedding Model</h2><p>For this project, we&rsquo;ll be using Xenova/all-MiniLM-L6-v2 for embeddings. This model is great at producing sentence-level embeddings, which work well for semantic search over documentation. It is relatively small and fast, and this makes it practical to run in Node.js without requiring a GPU. It outputs fixed 384-dimensional vectors (arrays with a fixed length of 384), which match our Qdrant collection configuration.</p><p>The model runs completely locally. On first use, Xenova downloads and caches it, and every subsequent run uses the cached version.</p><h2 id="building-the-embedding-service">Building the Embedding Service</h2><p>Our <code>EmbeddingService</code> will be responsible for converting text into vectors. Open the <code>embeddings.service.ts</code> file and update it with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  pipeline<span class="token punctuation">,</span>
  env<span class="token punctuation">,</span>
  FeatureExtractionPipeline<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@xenova/transformers'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">type</span> EmbeddingVector <span class="token operator">=</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">EmbeddingsService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> extractor<span class="token punctuation">:</span> FeatureExtractionPipeline <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly DIMENSION<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> vectorDimensionEnv <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
      <span class="token string">'QDRANT_VECTOR_DIMENSION'</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>vectorDimensionEnv<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>FeatureExtractionPipeline<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>extractor<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      env<span class="token punctuation">.</span>localModelPath <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
        <span class="token string">'HF_MODEL_CACHE'</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Loading embedding model (first time only, ~5s)...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> pipe <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">pipeline</span><span class="token punctuation">(</span>
        <span class="token string">'feature-extraction'</span><span class="token punctuation">,</span>
        <span class="token string">'Xenova/all-MiniLM-L6-v2'</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>extractor <span class="token operator">=</span> pipe<span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Embedding model loaded.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>extractor<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">embed</span><span class="token punctuation">(</span>text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>EmbeddingVector<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> extractor <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> output <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">extractor</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      pooling<span class="token punctuation">:</span> <span class="token string">'mean'</span><span class="token punctuation">,</span>
      normalize<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span>output<span class="token punctuation">.</span>data <span class="token keyword">as</span> Float32Array<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">embedBatch</span><span class="token punctuation">(</span>texts<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>EmbeddingVector<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> extractor <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> output <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">extractor</span><span class="token punctuation">(</span>texts<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      pooling<span class="token punctuation">:</span> <span class="token string">'mean'</span><span class="token punctuation">,</span>
      normalize<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span>output<span class="token punctuation">.</span>data <span class="token keyword">as</span> Float32Array<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span><span class="token punctuation">{</span> length<span class="token punctuation">:</span> texts<span class="token punctuation">.</span>length <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
      data<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>i <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION<span class="token punctuation">,</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">warmup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">embed</span><span class="token punctuation">(</span><span class="token string">'warmup'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Embedding model warmup completed.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Embedding model warmup failed:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">throw</span> error<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>extractor</code> property stores our loaded embedding model. It is initialized as <code>null</code> and loaded lazily on first use. This means the model only downloads when it is actually needed, rather than slowing down application startup.</p><p>The <code>getExtractor()</code> method loads and caches the model. First, we check if <code>this.extractor</code> already exists. If it does, we return it. If not, we set <code>env.localModelPath</code> to tell Xenova where to cache the downloaded model files, then call <code>pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')</code> to download and load the model.</p><p>The <code>embed()</code> method calls the extractor with two options: <code>pooling: 'mean'</code>, which averages all token embeddings into a single vector, and <code>normalize: true</code>, which scales the vector to unit length (required for cosine similarity in Qdrant). The extractor returns a Float32Array, which we convert to a regular array using <code>Array.from()</code>.</p><p>For <code>embedBatch()</code>, we pass an array of texts to the extractor. The model returns a flattened array containing all vectors concatenated together. We split this back into individual vectors by slicing out chunks of 384 values (our vector dimension). The first text gets indices 0&ndash;383, the second gets 384&ndash;767 and so on.</p><p>The <code>warmup()</code> method runs a dummy embedding to preload the model, preventing the first real user request from experiencing a delay while the model loads. Be sure to export the <code>EmbeddingsService</code> in the <code>EmbeddingsModule</code>.</p><h2 id="building-the-qdrant-service">Building the Qdrant Service</h2><p>This service wraps the vector database and handles creating the collection as well as reading and writing vectors. Update the <code>qdrant.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  QdrantClient<span class="token punctuation">,</span>
  Schemas<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@qdrant/js-client-rest'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IQdrantPayload</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token punctuation">[</span>key<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">]</span><span class="token punctuation">:</span> unknown<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IQdrantPoint</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  vector<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  payload<span class="token punctuation">:</span> IQdrantPayload<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">QdrantService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly client<span class="token punctuation">:</span> QdrantClient<span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly vectorDimension<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly collectionName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_URL'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'QDRANT_URL is not set in environment'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName <span class="token operator">=</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_COLLECTION'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">'documentation'</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> vectorDimensionEnv <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_VECTOR_DIMENSION'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>vectorDimension <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>vectorDimensionEnv<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">this</span><span class="token punctuation">.</span>client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">QdrantClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span> url <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">getCollectionName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> collections <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">getCollections</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> exists <span class="token operator">=</span> collections<span class="token punctuation">.</span>collections<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span>
      c <span class="token operator">=&gt;</span> c<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>exists<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Collection "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" already exists.`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">createCollection</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      vectors<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        size<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vectorDimension<span class="token punctuation">,</span>
        distance<span class="token punctuation">:</span> <span class="token string">'Cosine'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Created collection "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">".`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">upsertPoints</span><span class="token punctuation">(</span>points<span class="token punctuation">:</span> IQdrantPoint<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">upsert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      wait<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      points<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>
    vector<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span>
    filter<span class="token operator">?</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Schemas\<span class="token punctuation">[</span><span class="token string">'ScoredPoint'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> params<span class="token punctuation">:</span> Schemas<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
      vector<span class="token punctuation">,</span>
      limit<span class="token punctuation">,</span>
      with_payload<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      with_vector<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
      <span class="token operator">...</span><span class="token punctuation">(</span>filter <span class="token operator">&amp;&amp;</span> <span class="token punctuation">{</span> filter <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>We define an interface for our vector points. Each point has an ID, a vector and a payload. The payload holds metadata such as the text content and URL.</p><p>Our constructor reads our environment variables and creates a Qdrant client. The <code>setupCollection()</code> method checks if our collection exists and creates it if it doesn&rsquo;t. We use Cosine distance, which is the standard for semantic similarity.</p><p>The <code>upsertPoints()</code> method saves vectors and their metadata to Qdrant. Finally, the <code>search()</code> method finds similar vectors. We request the payload but not the vector itself, since we only need the metadata for displaying results.</p><h2 id="document-chunking">Document Chunking</h2><h3 id="document-processing-service">Document Processing Service</h3><p>LLMs and vector databases work best with smaller chunks of text. When large text, such as a 10-page document, is embedded as a single vector, specific details get lost.</p><p>While our Xenova model has a safe upper bound limit of approximately 2,000 characters, for best search quality it is advised to embed text with a length of 400&ndash;600 characters. Therefore, we need to split documents into chunks; for this project, we&rsquo;ll aim for around 500 characters.</p><p>Our chunking strategy will aim for the maximum number of complete paragraphs we can fit within the 500-character limit. Then we&rsquo;ll start a new chunk with an overlap from the end of the previous chunk. The purpose of the overlap is to preserve context across chunk boundaries.</p><p>Update your <code>document-processor.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IDocumentMetadata</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IDocumentChunk</span> <span class="token punctuation">{</span>
  text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  metadata<span class="token punctuation">:</span> IDocumentMetadata<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentProcessorService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly CHUNK_SIZE <span class="token operator">=</span> <span class="token number">500</span><span class="token punctuation">;</span> <span class="token comment">// characters</span>
  <span class="token keyword">private</span> readonly OVERLAP <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span>     <span class="token comment">// characters</span>


  <span class="token function">chunkDocument</span><span class="token punctuation">(</span>
    content<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    metadata<span class="token punctuation">:</span> IDocumentMetadata<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> chunks<span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> paragraphs <span class="token operator">=</span> content
      <span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\n\n'</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>p <span class="token operator">=&gt;</span> p<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>p <span class="token operator">=&gt;</span> p<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">let</span> currentChunk <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
    <span class="token keyword">let</span> chunkIndex <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> paragraph <span class="token keyword">of</span> paragraphs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> potentialChunk <span class="token operator">=</span> currentChunk
        <span class="token operator">?</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>currentChunk<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>paragraph<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
        <span class="token punctuation">:</span> paragraph<span class="token punctuation">;</span>

      <span class="token keyword">if</span> <span class="token punctuation">(</span>potentialChunk<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>CHUNK_SIZE <span class="token operator">&amp;&amp;</span> currentChunk<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Current chunk is full; emit it</span>
        chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
          text<span class="token punctuation">:</span> currentChunk<span class="token punctuation">,</span>
          chunkIndex<span class="token punctuation">,</span>
          metadata<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// Start new chunk with overlap (prefer complete sentence, fallback to word boundary)</span>
        <span class="token keyword">const</span> overlap <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">findOverlap</span><span class="token punctuation">(</span>currentChunk<span class="token punctuation">)</span><span class="token punctuation">;</span>
        currentChunk <span class="token operator">=</span> overlap <span class="token operator">+</span> <span class="token string">'\n\n'</span> <span class="token operator">+</span> paragraph<span class="token punctuation">;</span>
        chunkIndex<span class="token operator">++</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        currentChunk <span class="token operator">=</span> potentialChunk<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// Emit last chunk</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>currentChunk<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        text<span class="token punctuation">:</span> currentChunk<span class="token punctuation">,</span>
        chunkIndex<span class="token punctuation">,</span>
        metadata<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> chunks<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">findOverlap</span><span class="token punctuation">(</span>text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> searchWindow <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// Try to find last complete sentence</span>
    <span class="token keyword">const</span> sentenceMatch <span class="token operator">=</span> searchWindow<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex">/[.!?]\s+([^.!?]+)$/</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>sentenceMatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> sentenceMatch<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token comment">// Fallback: find word boundary near target overlap length</span>
    <span class="token keyword">const</span> tail <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP <span class="token operator">*</span> <span class="token number">1.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> wordMatch <span class="token operator">=</span> tail<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex">/\s+(\S+.*)$/</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>wordMatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> wordMatch<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token comment">// Last resort: from last space</span>
    <span class="token keyword">const</span> lastSpace <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">lastIndexOf</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">,</span> text<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> lastSpace <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">?</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>lastSpace <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above, we set our chunk size to 500 characters with a 50-character overlap. The overlap helps preserve context across chunk boundaries.</p><p>The <code>chunkDocument()</code> method splits text by paragraph, then accumulates paragraphs until the next one would exceed our limit. It then saves the chunk and starts a new one with an overlap from the end of the chunk that was just saved.</p><p>The <code>findOverlap()</code> method tries to find a complete sentence for the overlap first. If that fails, it looks for a word boundary. This keeps the overlap readable rather than cutting words in half.</p><h3 id="document-ingestion-service">Document Ingestion Service</h3><p>This service processes raw documents, converts them to vectors and saves them in Qdrant. Update your <code>document-ingestion.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> v4 <span class="token keyword">as</span> uuidv4 <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'uuid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DocumentProcessorService<span class="token punctuation">,</span> IDocumentMetadata<span class="token punctuation">,</span> IDocumentChunk <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../document-processor/document-processor.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService<span class="token punctuation">,</span> EmbeddingVector <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../../embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService<span class="token punctuation">,</span> IQdrantPoint<span class="token punctuation">,</span> IQdrantPayload <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../../qdrant/qdrant.service'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IRawDocument</span> <span class="token keyword">extends</span> <span class="token class-name">IDocumentMetadata</span> <span class="token punctuation">{</span>
  content<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentIngestionService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> readonly processor<span class="token punctuation">:</span> DocumentProcessorService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly embeddings<span class="token punctuation">:</span> EmbeddingsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly qdrant<span class="token punctuation">:</span> QdrantService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">/**
   * Ingest one or more raw documents:
   * - Chunk content into smaller overlapping pieces.
   * - Embed all chunk texts in a batch.
   * - Upsert points (vector + payload) into Qdrant.
   */</span>
  <span class="token keyword">async</span> <span class="token function">ingestDocuments</span><span class="token punctuation">(</span>docs<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token punctuation">{</span>
    status<span class="token punctuation">:</span> <span class="token string">'ok'</span> <span class="token operator">|</span> <span class="token string">'error'</span><span class="token punctuation">;</span>
    documents<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    totalChunks<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    skipped<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    error<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>docs<span class="token operator">?</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token punctuation">:</span> <span class="token string">'ok'</span><span class="token punctuation">,</span> documents<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> totalChunks<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> skipped<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">let</span> totalChunks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
      <span class="token keyword">let</span> skipped <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> doc <span class="token keyword">of</span> docs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">ingestDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          totalChunks <span class="token operator">+=</span> result<span class="token punctuation">.</span>chunks<span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
          skipped<span class="token operator">++</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        status<span class="token punctuation">:</span> <span class="token string">'ok'</span><span class="token punctuation">,</span>
        documents<span class="token punctuation">:</span> docs<span class="token punctuation">.</span>length <span class="token operator">-</span> skipped<span class="token punctuation">,</span>
        totalChunks<span class="token punctuation">,</span>
        skipped<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Fatal error during ingestion:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        status<span class="token punctuation">:</span> <span class="token string">'error'</span><span class="token punctuation">,</span>
        documents<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        totalChunks<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        skipped<span class="token punctuation">:</span> docs<span class="token operator">?</span><span class="token punctuation">.</span>length <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">,</span>
        error<span class="token punctuation">:</span> error <span class="token keyword">instanceof</span> <span class="token class-name">Error</span> <span class="token operator">?</span> error<span class="token punctuation">.</span>message <span class="token punctuation">:</span> <span class="token string">'Unknown error'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">ingestDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token punctuation">{</span>
    success<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
    chunks<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>doc<span class="token punctuation">.</span>title <span class="token operator">||</span> <span class="token operator">!</span>doc<span class="token punctuation">.</span>content<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">'Skipping document, missing title or content'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> metadata<span class="token punctuation">:</span> IDocumentMetadata <span class="token operator">=</span> <span class="token punctuation">{</span>
        title<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>category <span class="token operator">||</span> <span class="token string">'uncategorized'</span><span class="token punctuation">,</span>
        url<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>url <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>

      <span class="token keyword">const</span> chunks <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>processor<span class="token punctuation">.</span><span class="token function">chunkDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>content<span class="token punctuation">,</span> metadata<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>chunks<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Skipping "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" - produced no chunks`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> vectors <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>embeddings<span class="token punctuation">.</span><span class="token function">embedBatch</span><span class="token punctuation">(</span>
        chunks<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>chunk <span class="token operator">=&gt;</span> chunk<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">if</span> <span class="token punctuation">(</span>vectors<span class="token punctuation">.</span>length <span class="token operator">!==</span> chunks<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token string">`Error ingesting "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">": expected </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>chunks<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> vectors, got </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>vectors<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> points <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createQdrantPoints</span><span class="token punctuation">(</span>chunks<span class="token punctuation">,</span> vectors<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">upsertPoints</span><span class="token punctuation">(</span>points<span class="token punctuation">)</span><span class="token punctuation">;</span>

      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Ingested "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>chunks<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> chunks).`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> chunks<span class="token punctuation">.</span>length <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Error ingesting "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">":`</span></span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">createQdrantPoints</span><span class="token punctuation">(</span>
    chunks<span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    vectors<span class="token punctuation">:</span> EmbeddingVector<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> IQdrantPoint<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> chunks<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>chunk<span class="token punctuation">,</span> index<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token function">uuidv4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      vector<span class="token punctuation">:</span> vectors<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">,</span>
      payload<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        title<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>category<span class="token punctuation">,</span>
        url<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>url<span class="token punctuation">,</span>
        text<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>text<span class="token punctuation">,</span>
        chunkIndex<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>chunkIndex<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In our constructor, we inject three services: the document processor for chunking, the embeddings service for generating vectors and the Qdrant service for database operations.</p><p>The <code>ingestDocuments()</code> method processes multiple documents at once. First, it confirms that the Qdrant collection is set up, then it processes each document individually. If one document fails, we still proceed with the others while tracking which documents were skipped and which were successful.</p><p>The <code>ingestDocument()</code> method handles the actual ingestion for individual documents. It verifies that the document has the required fields, sets up metadata, chunks the content, generates embeddings and saves them to Qdrant. It also confirms that the number of vectors is consistent with the number of chunks; if not, it sends a warning and skips that document.</p><p>The <code>createQdrantPoints()</code> method is a helper that combines our chunks, vectors and metadata into the format Qdrant expects.</p><h3 id="documents-controller-and-module">Documents Controller and Module</h3><p>Update your <code>documents.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Body<span class="token punctuation">,</span> Controller<span class="token punctuation">,</span> Post <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DocumentIngestionService<span class="token punctuation">,</span> IRawDocument <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./document-ingestion/document-ingestion.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'documents'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentsController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly ingestionService<span class="token punctuation">:</span> DocumentIngestionService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  @<span class="token function">Post</span><span class="token punctuation">(</span><span class="token string">'ingest'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">ingest</span><span class="token punctuation">(</span>@<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> docs<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>body<span class="token operator">?</span><span class="token punctuation">.</span>docs<span class="token operator">?</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'No documents provided'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ingestionService<span class="token punctuation">.</span><span class="token function">ingestDocuments</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span>docs<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Next, update the <code>DocumentsModule to import the EmbeddingsModule</code> and <code>QdrantModule</code>.</p><h2 id="building-the-search-service">Building the Search Service</h2><p>The search service handles user queries. Update your <code>search.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService<span class="token punctuation">,</span> IQdrantPayload <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../qdrant/qdrant.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Schemas <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@qdrant/js-client-rest'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ISearchResult</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  snippet<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  score<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SearchService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly MIN_LIMIT <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly MAX_LIMIT <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly DEFAULT_LIMIT <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> readonly embeddings<span class="token punctuation">:</span> EmbeddingsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly qdrant<span class="token punctuation">:</span> QdrantService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">mapHits</span><span class="token punctuation">(</span>hits<span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'ScoredPoint'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> hits
      <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>hit <span class="token operator">=&gt;</span> hit<span class="token punctuation">.</span>payload <span class="token operator">&amp;&amp;</span> hit<span class="token punctuation">.</span>score <span class="token operator">!==</span> undefined<span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>hit <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> payload <span class="token operator">=</span> hit<span class="token punctuation">.</span>payload <span class="token keyword">as</span> IQdrantPayload<span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>
          title<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>title <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          snippet<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>text <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          url<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>url <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          category<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>category <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          score<span class="token punctuation">:</span> hit<span class="token punctuation">.</span>score <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token comment">//similarity score</span>
          chunkIndex<span class="token punctuation">:</span> <span class="token function">Number</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>chunkIndex <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> trimmed <span class="token operator">=</span> query<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> trimmed <span class="token operator">&amp;&amp;</span> trimmed<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token operator">?</span> trimmed <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>SearchService<span class="token punctuation">.</span>MIN_LIMIT<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>SearchService<span class="token punctuation">.</span>MAX_LIMIT<span class="token punctuation">,</span> limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">createCategoryFilter</span><span class="token punctuation">(</span>category<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> undefined<span class="token punctuation">)</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span> <span class="token operator">|</span> undefined <span class="token punctuation">{</span>
    <span class="token keyword">const</span> trimmed <span class="token operator">=</span> category<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>trimmed<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> undefined<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      must<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
          key<span class="token punctuation">:</span> <span class="token string">'category'</span><span class="token punctuation">,</span>
          match<span class="token punctuation">:</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> trimmed <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">performSearch</span><span class="token punctuation">(</span>
    query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span>
    filter<span class="token operator">?</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> vector <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>embeddings<span class="token punctuation">.</span><span class="token function">embed</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> hits <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span>vector<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> filter<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">mapHits</span><span class="token punctuation">(</span>hits<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Error performing search:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  
  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> limit <span class="token operator">=</span> SearchService<span class="token punctuation">.</span>DEFAULT_LIMIT<span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> normalizedQuery <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>normalizedQuery<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">performSearch</span><span class="token punctuation">(</span>normalizedQuery<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">searchWithCategory</span><span class="token punctuation">(</span>
    query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    category<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
    limit <span class="token operator">=</span> SearchService<span class="token punctuation">.</span>DEFAULT_LIMIT<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> normalizedQuery <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>normalizedQuery<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createCategoryFilter</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">performSearch</span><span class="token punctuation">(</span>normalizedQuery<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">,</span> filter<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>mapHits()</code> method converts Qdrant&rsquo;s raw response into a user-friendly format. The <code>validateAndNormalizeQuery()</code> method verifies we have an actual query string, <code>clampLimit()</code> keeps result counts within safe limits and <code>createCategoryFilter()</code> builds the Qdrant filter object when users filter by category.</p><p>The <code>performSearch()</code> method embeds the user query, searches Qdrant and maps the results.</p><p>The <code>search()</code> method uses only pure semantic search with no filters, while <code>searchWithCategory()</code> adds category filtering for more specific searches.</p><h2 id="search-controller">Search Controller</h2><p>Our search controller exposes two endpoints. The <code>/search</code> endpoint provides pure semantic search, while <code>/search/hybrid</code> adds category filtering. Update your <code>search.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Controller<span class="token punctuation">,</span> Get<span class="token punctuation">,</span> Query <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> SearchService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./search.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'search'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SearchController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly searchService<span class="token punctuation">:</span> SearchService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> undefined<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> parsed <span class="token operator">=</span> <span class="token function">Number</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">isNaN</span><span class="token punctuation">(</span>parsed<span class="token punctuation">)</span> <span class="token operator">||</span> parsed <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token number">10</span> <span class="token punctuation">:</span> parsed<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'q'</span><span class="token punctuation">)</span> q<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'limit'</span><span class="token punctuation">)</span> limit<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>q<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'Query parameter "q" is required'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>searchService<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span>q<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">'hybrid'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">hybrid</span><span class="token punctuation">(</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'q'</span><span class="token punctuation">)</span> q<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'category'</span><span class="token punctuation">)</span> category<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'limit'</span><span class="token punctuation">)</span> limit<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>q<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'Query parameter "q" is required'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>searchService<span class="token punctuation">.</span><span class="token function">searchWithCategory</span><span class="token punctuation">(</span>q<span class="token punctuation">,</span> category<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Finally, update the <code>SearchModule</code> to import the <code>EmbeddingsModule</code> and <code>QdrantModule</code>.</p><h2 id="application-warmup">Application Warmup</h2><p>We don&rsquo;t want the first user request to hang while our ML model loads, so we&rsquo;ll add a warmup phase during application startup.</p><p>Update your <code>main.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> NestFactory <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AppModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./qdrant/qdrant.service'</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">await</span> NestFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>AppModule<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Starting services warmup...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> embeddings <span class="token operator">=</span> app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>EmbeddingsService<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> qdrant <span class="token operator">=</span> app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>QdrantService<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      embeddings<span class="token punctuation">.</span><span class="token function">warmup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      qdrant<span class="token punctuation">.</span><span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'✓ Services ready.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Warmup failed'</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>
    process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This loads the ML model and sets up the database collection before accepting requests.</p><h2 id="testing-the-api">Testing the API</h2><p>Run the following in your terminal to start your server:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start:dev
</code></pre><p>You should see the warmup logs followed by the server start message.</p><h3 id="ingesting-documents">Ingesting Documents</h3><p>Let&rsquo;s add some test documents:</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/documents/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "docs": [
      {
        "title": "API Authentication",
        "category": "Security",
        "url": "/docs/auth",
        "content": "To access the API, you must use a Bearer token in the header. Tokens expire after 1 hour."
      },
      {
        "title": "Rate Limiting",
        "category": "Performance",
        "url": "/docs/rate-limits",
        "content": "We limit requests to 100 per minute per IP address. Exceeding this returns a 429 Too Many Requests error."
      }
    ]
  }'
</code></pre><p>Your response should be:</p><pre class=" language-shell"><code class="prism  language-shell">{
  "status": "ok",
  "documents": 2,
  "totalChunks": 2,
  "skipped": 0
}
</code></pre><h3 id="searching-documents">Searching Documents</h3><p>Let&rsquo;s try a semantic search. Note that we&rsquo;re not using the exact words &ldquo;Bearer&rdquo; or &ldquo;header&rdquo;:</p><pre class=" language-shell"><code class="prism  language-shell">curl "http://localhost:3000/search?q=how%20do%20I%20log%20in%20to%20the%20api"
</code></pre><p>The system understands that &ldquo;log in&rdquo; is semantically related to &ldquo;authentication&rdquo; and &ldquo;Bearer token,&rdquo; so it returns the Authentication document.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/sample-response-for-semantic-search.png?sfvrsn=d20d142e_2" title="Sample response for semantic search" alt="Sample response for semantic search" /></p><h3 id="testing-hybrid-search">Testing Hybrid Search</h3><p>Let&rsquo;s try searching with a category filter:</p><pre class=" language-shell"><code class="prism  language-shell">curl "http://localhost:3000/search/hybrid?q=api%20limits&amp;category=Performance"
</code></pre><p>Your response should be:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/sample-response-for-category-filter.png?sfvrsn=29ba3928_2" title="Sample response for category filter" alt="Sample response for category filter" /></p><p>This searches for content semantically related to &ldquo;api limits&rdquo; but only returns results in the &ldquo;Performance&rdquo; category.</p><h2 id="conclusion">Conclusion</h2><p>In this article, we learned how to run a vector database locally with Docker, generate embeddings without external APIs, chunk documents with overlapping windows and combine vector similarity with metadata filtering.</p><p>Possible next steps include swapping Xenova for OpenAI if we need larger models, or moving Qdrant to a cloud cluster for millions of vectors, while preserving the existing NestJS logic.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">What We&rsquo;ve Learned Designing Dev Tools in the Age of AI</h4></div><div class="col-8"><p class="u-fs16 u-mb0">AI didn&rsquo;t just change how we write code. It changed how we build products. <a target="_blank" href="https://www.telerik.com/blogs/what-weve-learned-designing-dev-tools-ai">Hear four lessons from real tech professionals building dev tools.</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17336853.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bb2fa99e-0c29-407f-9eb9-4d2b43a9632a</id>
    <title type="text">Creating More Realistic Tests with In-Memory Databases in ASP.NET Core</title>
    <summary type="text">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</summary>
    <published>2026-05-06T17:20:57Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17335641/creating-more-realistic-tests-memory-databases-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</span></p><p>Testing a web API involves much more than simply checking whether methods return or send data correctly. It requires validating real-world behaviors like pagination, unique keys, data persistence and other database rules. Anticipating and reproducing these scenarios can be challenging, especially when they depend on specific query behaviors or stored data.</p><p>To address this, the .NET ecosystem provides powerful tools for unit testing. In this post, we explore how in-memory SQLite serves as an effective solution for testing ASP.NET Core APIs, enabling developers to simulate common scenarios without deploying a real database. Additionally, we demonstrate how tools like Progress Telerik JustMock can streamline test implementation and help validate business rules efficiently.</p><h2 id="the-importance-of-testing-in-real-world-environments">The Importance of Testing in Real-World Environments</h2><p>A common example of functionality in web APIs is paginated search, which at first glance is quite simple. We receive parameters such as <code>page</code> and <code>pageSize</code>, apply a Skip and a Take, return the data and move on. In a controlled scenario, with few records, it&rsquo;s difficult for something to go wrong.</p><p>The problem begins when this code reaches production or even test environments, without having been validated with a real database. Consider a common scenario: in a paginated search, sorting is essential for it to function correctly. Without an explicit <code>OrderBy</code>, the database does not guarantee the order of the returned records.</p><p>In in-memory lists, the result usually appears stable, which completely masks the problem. Without tests running this query on a relational database, the error goes unnoticed, resulting in inconsistent pagination with duplicate records between pages, items &ldquo;disappearing&rdquo; when navigating between pages or different results for the same request. This type of bug is especially difficult to reproduce locally when not using a real database or even a simulation of one.</p><p>This is where the in-memory database plays a very important role. It allows the query to be executed exactly as it will be executed in production, revealing flaws that only appear when pagination, sorting and the database work together.</p><h2 id="understanding-in-memory-sqlite">Understanding In-Memory SQLite</h2><p>In-memory SQLite is a way to use the SQLite database entirely in memory. Instead of saving data to a <code>.db</code> file, SQLite creates the database only while the application or connection is active. When the connection is closed, all data is discarded.</p><p>In-memory SQLite is widely used in automated testing, especially in ASP.NET Core applications in conjunction with Entity Framework Core, as it has advanced features such as simulating a real database and executing real SQL (constraints, indexes, unique keys, FK and others).</p><h2 id="testing-with-sqlite-in-memory">Testing with SQLite in Memory</h2><p>Next, we&rsquo;ll look at some examples of unit tests in common web API scenarios where in-memory SQLite excels. But first, let&rsquo;s create the basic application and then add the tests.</p><p>You can access all the code discussed in this post in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/memo-order">Memo Order source code</a>.</p><p>To create the application you can use the command below:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new web -o MemoOrder
</code></pre><p>To add the NuGet packages you can use the following commands:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet add package Microsoft.EntityFrameworkCore --version 10.0.1
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.1
</code></pre><p>Then, open the application and create the following classes:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> TotalAmount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> DateTime CreatedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">bool</span> IsDeleted <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderSummaryDto</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AppDbContext</span> <span class="token punctuation">:</span> DbContext
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">AppDbContext</span><span class="token punctuation">(</span>DbContextOptions<span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span> options<span class="token punctuation">)</span>
        <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> DbSet<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> Orders <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token generic-method function">Set<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span>ModelBuilder modelBuilder<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        modelBuilder<span class="token punctuation">.</span><span class="token generic-method function">Entity<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>entity <span class="token operator">=</span><span class="token operator">&gt;</span>
        <span class="token punctuation">{</span>
            entity<span class="token punctuation">.</span><span class="token function">HasKey</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">HasIndex</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsUnique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
entity<span class="token punctuation">.</span><span class="token function">HasQueryFilter</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderRepository</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> AppDbContext _context<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">OrderRepository</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context <span class="token operator">=</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync</span><span class="token punctuation">(</span>Order order<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">Pagination</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">,</span> <span class="token keyword">int</span> skip<span class="token punctuation">,</span> <span class="token keyword">int</span> take<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
                    <span class="token punctuation">.</span><span class="token function">OrderBy</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>skip<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span>take<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now let&rsquo;s create a special factory class that will be used in the tests to create the in-memory database and keep the connection open while the tests are running. Create the following class in the application:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Data<span class="token punctuation">.</span>Sqlite<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SqliteInMemoryFactory</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> AppDbContext <span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqliteConnection</span><span class="token punctuation">(</span><span class="token string">"DataSource=:memory:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        connection<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DbContextOptionsBuilder</span><span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">UseSqlite</span><span class="token punctuation">(</span>connection<span class="token punctuation">)</span>
            <span class="token punctuation">.</span>Options<span class="token punctuation">;</span>

        <span class="token keyword">var</span> context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AppDbContext</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">EnsureCreated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that to tell SQLite to use an in-memory database, we use <code>DataSource=:memory:</code> in the connection string.</p><h3 id="creating-the-test-project">Creating the Test Project</h3><p>All the code needed to implement the unit tests is ready, so let&rsquo;s create the test project and download the NuGet packages for it. To do this, you can use the following commands, simply run them in a terminal in the application&rsquo;s root directory:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new xunit -n MemoOrder.Tests
<span class="token function">cd</span> MemoOrder.Tests
dotnet add package Microsoft.NET.Test.Sdk --version 18.0.1
dotnet add package xunit --version 2.9.3
dotnet add package xunit.runner.visualstudio --version 3.1.5
dotnet add package coverlet.collector --version 6.0.4
dotnet add reference <span class="token punctuation">..</span>/MemoOrder/MemoOrder.csproj
</code></pre><p>Next, inside the project MemoOrder.Tests, create a new class called OrderRepositoryTests and let&rsquo;s add tests to it.</p><h3 id="testing-persistence">Testing Persistence</h3><p>The first test will be used to validate persistence&mdash;that is, to insert data into the database (which in this case will be the in-memory database). To do this, simply add the following test method to the <code>OrderRepositoryTests</code> class:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync_ShouldPersistOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> 200m<span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    <span class="token keyword">var</span> savedOrder <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">FirstAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>200m<span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that we use <code>var context = SqliteInMemoryFactory.Create();</code> to create and represent the database in memory. It works exactly like the EF Core context that maps database tables to class entities.</p><h3 id="testing-constraint-unique">Testing Constraint UNIQUE</h3><p>Another common mistake is allowing duplicate records to reach the production environment, which in many cases can have catastrophic impacts. With in-memory testing, we can anticipate problems like this. Simply create a test that verifies inserting a duplicate record will generate an exception. In this case, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Throw_When_Duplicated_Order_Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> Assert<span class="token punctuation">.</span><span class="token generic-method function">ThrowsAsync<span class="token punctuation">&lt;</span>DbUpdateException<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we created an instance of the database in memory, added an item to the table and saved it.</p><p>Then, we tried to add the same item, which generates an exception because, in the dbContext configuration, we declared the Number property of the Order entity as Unique: <code>entity.HasIndex(o =&gt; o.Number).IsUnique();</code>.</p><p>Thus, we have a real-world scenario, demonstrating that duplicate records will not reach the production environment, as the unit test is able to reproduce exactly the same result.</p><h3 id="testing-pagination">Testing Pagination</h3><p>To test pagination using an in-memory database, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Return_Paginated_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> $<span class="token string">"ORD-{i}"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> i <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> page <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">Pagination</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-6"</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that here we create 10 items and insert them into the in-memory database. Then, we use the <code>Pagination(context, 5, 5)</code> method to perform the pagination and finally verify if the expected quantity and item are correct.</p><p>An in-memory database wouldn&rsquo;t be mandatory to test pagination. This could be done using only regular in-memory lists. However, in this way, we can reproduce the same production scenario.</p><h3 id="testing-soft-delete">Testing Soft Delete</h3><p>Soft-delete is a technique where data is not physically removed from the database, but rather marked as inactive through a flag, such as an <code>isDeleted</code> or <code>deleted_at</code> column. This allows you to keep the entire transaction history in the database.</p><p>The problem is that in some cases soft delete can leak unwanted data into reports, new endpoints or even refactored queries. To prevent this from happening, we can use the in-memory database and simulate a search with and without soft-delete:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
 <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">SoftDeleted_Orders_Should_Not_Appear_In_Default_Queries</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token punctuation">{</span>
     <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
         <span class="token punctuation">}</span><span class="token punctuation">,</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
         <span class="token punctuation">}</span>
     <span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     Assert<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>
     Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-001"</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
</code></pre><p>In this test, the list returns only one item because the second one is marked as deleted. This happens because we declared the query filter <code>entity.HasQueryFilter(o =&gt; !o.IsDeleted);</code> when the database is created. By using an in-memory database, we can verify this mechanism works.</p><p>It&rsquo;s also possible to ignore the query filter and still verify it works:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Admin_Query_Should_See_SoftDeleted_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">IgnoreQueryFilters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="testing-rollback">Testing Rollback</h3><p>When performing multiple operations, we run the risk of something failing during the process and the data becoming inconsistent. To keep this from happening, we can use the in-memory database to test if the rollback operation solves this problem.</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Transaction_Should_Rollback_When_Error_Occurs</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">using</span> <span class="token keyword">var</span> transaction <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">BeginTransactionAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-NMBR-1"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">RollbackAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> count <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">CountAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we added an item to the order list, saved it and then performed a rollback. Finally, we verified that the list was empty, checking that the operation was undone.</p><h3 id="testing-query-refactoring">Testing Query Refactoring</h3><p>Refactoring queries is often necessary, whether to improve performance or simply to make the code cleaner. The problem is that this can break queries that previously worked.</p><p>To see that both the old and new queries have the same result, we can use an in-memory database as demonstrated in the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Refactored_Query_Should_Return_Same_Result</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> oldResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> newResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> o<span class="token punctuation">.</span>TotalAmount <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>oldResult<span class="token punctuation">,</span> newResult<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>In this example, we verify that both queries have the same result, so even after refactoring, the old logic will continue to work.</p><h3 id="query-test-with-projection-to-dto">Query Test with Projection to DTO</h3><p>When using Data Transfer Objects (DTOs) for data transport, we are subject to errors that can silently break our queries. Again, with an in-memory database, we can simulate a query using a DTO.</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Projection_To_Dto_Should_Work</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token class-name">OrderSummaryDto</span>
            <span class="token punctuation">{</span>
                Number <span class="token operator">=</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">,</span>
                Total <span class="token operator">=</span> o<span class="token punctuation">.</span>TotalAmount
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">NotNull</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>Note that through this test, we can be sure that our DTOs will function correctly.</p><h2 id="not-all-tests-need-a-database">Not All Tests Need a Database</h2><p>Until now, we&rsquo;ve used in-memory SQLite to validate constraints (UNIQUE), global filters (Soft Delete), real pagination, GroupBy, Sum, transactions and rollback.</p><p>Tests like these depend on database behavior, but it&rsquo;s common to encounter scenarios where the database isn&rsquo;t present, and only business rules are involved. In this case, to facilitate our work and validate our tests, we can use tools like <a target="_blank" href="https://www.telerik.com/products/mocking.aspx">JustMock</a> and simulate a production-facing environment.</p><p>Next, we&rsquo;ll look at some examples of business rule tests where we can use JustMock to easily create mocks for our tests.</p><h3 id="verifying-the-repository-will-be-called">Verifying the Repository Will Be Called</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">CreateAsync_Should_Call_Repository_AddAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Id <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> service<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-gethighvalueorders-method-returns-only-orders-above-the-minimum-value">Verifying the GetHighValueOrders Method Returns Only Orders Above the Minimum Value</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Return_Only_Orders_Above_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token function">FakeOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">var</span> result <span class="token operator">=</span> service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Assert<span class="token punctuation">.</span><span class="token function">AreEqual</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">IsTrue</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-service-forwards-exactly-the-filter-received">Verifying the Service Forwards Exactly the Filter Received</h3><pre class=" language-csharp"><code class="prism  language-csharp">       <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Call_Repository_With_Same_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// Arrange</span>
            <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Act</span>
            service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Assert</span>
            Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
</code></pre><p>If you run all the tests, you can verify that they all passed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/all-tests-passed.png?sfvrsn=60e45a83_2" title="all tests passed" alt="All tests passed" /></p><h2 id="conclusion">Conclusion</h2><p>Testing web applications is common in software development, but these tests don&rsquo;t always guarantee that errors won&rsquo;t occur in the production environment. Using in-memory databases, such as in-memory SQLite, allows you to reproduce scenarios closer to reality.</p><p>In this post, we saw how this approach helps identify common errors, such as persistence, pagination and projection issues. Furthermore, we explored how JustMock can help save time when validating business rules. I hope the content covered here helps you further improve your applications, making them more reliable through even more realistic testing.</p><hr /><p><a href="https://www.telerik.com/try/justmock" target="_blank">Try JustMock</a> free for 30 days, or <a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank">try out the whole Telerik DevCraft</a> suite for access to the ASP.NET Core component library and lots more (also a free 30-day trial).</p><img src="https://feeds.telerik.com/link/10827/17335641.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2c9543f6-911a-443f-8612-e1d7e336574b</id>
    <title type="text">Routing Management and Creating NotFound Pages in Blazor</title>
    <summary type="text">Route handling got better in .NET 10. Let’s see how to create context for managing information on an error page and show users specific error pages.</summary>
    <published>2026-05-05T15:57:00Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17334896/routing-management-creating-notfound-pages-blazor"/>
    <content type="text"><![CDATA[<p><span class="featured">Route handling got better in .NET 10. Let&rsquo;s see how to create context for managing information on an error page and show users specific error pages.</span></p><p>If you have worked with Blazor in versions prior to .NET 10, you have certainly encountered some difficulties in implementing not found pages and in handling NavLink to show users the page they are currently on.</p><p>This is why, during the last update to .NET 10, the team behind Blazor has significantly improved the handling of routes and the management of not found pages. In this article, we will analyze the most important aspects. Let&rsquo;s get started!</p><h2 id="understanding-the-handling-of-not-found-routes-in-blazor">Understanding the Handling of Not Found Routes in Blazor</h2><p>When we talk about routing, we refer to the mechanism that allows the user to navigate to a specific component based on the requested URL. However, there may be times when there are non-existent URLs that the user attempts to navigate to, such as when an item has been deleted or when, for some reason, a page has been removed from the site.</p><p>Properly handling these scenarios is essential for a good user experience, good SEO and, in general, having a reliable web application.</p><h2 id="the-problem-with-not-found-pages-before-.net-10">The Problem with Not Found Pages Before .NET 10</h2><p>You should know that before .NET 10, as part of the <code>Router</code> component, there was a render fragment called <code>NotFound</code>, which at first glance could lead us to think that it allowed defining graphical code to show content when a route was not found:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Found</span> <span class="token attr-name">Context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>routeData<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        ...
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Found</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NotFound</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>LayoutView</span> <span class="token attr-name">Layout</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Layout.MainLayout)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Page not found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, but there is nothing here!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>LayoutView</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NotFound</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In practice, something different occurred, as when attempting to navigate to a non-existent page, an error page was displayed instead of the defined graphical code:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/net9-render-fragment-notfound-logic.gif?sfvrsn=d61310e6_2" alt="Execution flow of &#39;NotFound&#39; RenderFragment in .NET 9 routing" /></p><p>Another technique that was used before .NET 10 was to handle the not found page code within the components. For example, there could be an ecommerce-type application with a products page and a product details page. On the product details page, if the user tried to access a non-existent page, it was handled similarly to the following way:</p><pre class=" language-xml"><code class="prism  language-xml">@if (product == null)
{    
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert alert-warning text-center<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h4</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert-heading<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Product Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h4</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>The product with ID <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>@Id<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> does not exist in our catalog.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>hr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-primary<span class="token punctuation">"</span></span> <span class="token attr-name">@onclick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>GoBack<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token entity" title="←">&amp;larr;</span> Back to Products
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
}
else
{
    ...
}
</code></pre><p>The execution of the previous code resulted in the following appearance:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/handling-notfound-errors-net9.png?sfvrsn=8f3d31f2_2" alt="Handling Not Found errors in .NET 9" /></p><p>The previous code, although it achieved the goal of showing the user that the product did not exist, had several problems:</p><ul><li>It returned an HTTP 200 code, not a 404.</li><li>Each component had to implement its own logic.</li><li>If there was navigation to a non-existent URL (not handled by a component), a site error page was shown.</li></ul><p>Next, let&rsquo;s see how .NET 10 improves the handling of not found pages.</p><h2 id="features-in-.net-10-for-route-management">Features in .NET 10 for Route Management</h2><p>In .NET 10, things have changed regarding route management. Let&rsquo;s analyze what these new features are.</p><h3 id="the-router.notfoundpage-parameter">The Router.NotFoundPage Parameter</h3><p>One of the most prominent updates is the addition of the <code>NotFoundPage</code> parameter to the <code>Router</code> component, which allows specifying a Razor page component that will be displayed when a route is not found. By default, in Blazor projects starting from .NET 10, in the <code>Pages</code> folder we find a component called <code>NotFound.razor</code>, whose structure is as follows:</p><p><strong>NotFound.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, the content you are looking for does not exist.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p><strong>Routes.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span> <span class="token attr-name">NotFoundPage</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Pages.NotFound)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
   ...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The preview of this code gives the following result:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/default-notfoundpage-component-net10.png?sfvrsn=8d90d029_2" alt="Default NotFoundPage component behavior in .NET 10" /></p><p>In the code above, we can see that the 404 error page must include the <code>@page</code> directive for it to function correctly.</p><h3 id="customizing-the-notfound-component">Customizing the NotFound Component</h3><p>With the <code>NotFound.razor</code> component showing us how to implement a 404 page in our Blazor apps, the next step is to customize it. There are several ways to do this. For example, by adding support for contextual messages through a service class called <code>NotFoundContext</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NotFoundContext</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Heading <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Message <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">UpdateContext</span><span class="token punctuation">(</span><span class="token keyword">string</span> heading<span class="token punctuation">,</span> <span class="token keyword">string</span> message<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Heading <span class="token operator">=</span> heading<span class="token punctuation">;</span>
        Message <span class="token operator">=</span> message<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>This class will allow you to show a custom heading and message based on the operation the user has requested. For it to work correctly, you need to register the service in <code>Program.cs</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddScoped<span class="token punctuation">&lt;</span>NotFoundContext<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Now, let&rsquo;s give a bit more design to the <code>NotFound.razor</code> template:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout
@using NotFoundManagementNET102.Services
@inject NotFoundContext NotFoundContext

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>container mt-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>row justify-content-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>col-md-8 col-lg-6<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card shadow-sm border-0 text-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-body p-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#6c757d<span class="token punctuation">"</span></span>
                             <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-exclamation-triangle<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0 0 16 16<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016...<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-title text-dark mb-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Heading ?? "Page Not Found")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-text text-muted mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Message ?? "Sorry, the content you are looking for does not exist.")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>d-flex justify-content-center gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/products<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-primary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token entity" title="←">&amp;larr;</span> Back to Products
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-secondary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            Go Home
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In the code above, you can notice that it checks whether the user sets a value for <code>Heading</code> and <code>Message</code>, in order to display either a generic message or a personalized one. Let&rsquo;s see next how to display custom pages based on the user&rsquo;s context.</p><h3 id="handling-not-found-pages-with-navigationmanager.notfound-and-onnotfound">Handling Not Found Pages with NavigationManager.NotFound() and OnNotFound</h3><p>In .NET 9, <code>NavigationManager.NotFound()</code> and <code>OnNotFound</code> were introduced as part of the framework improvements for handling not found pages. We can combine both with the <code>NotFoundContext</code> service to display a 404 response and custom messages, through the following code:</p><pre class=" language-csharp"><code class="prism  language-csharp">@page <span class="token string">"/product/{Id:int}"</span>
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Models
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Services
@implements <span class="token class-name">IDisposable</span>
@inject ProductService ProductService
@inject NavigationManager Navigation
@inject NotFoundContext NotFoundContext

<span class="token operator">&lt;</span>PageTitle<span class="token operator">&gt;</span>@<span class="token punctuation">(</span>product<span class="token operator">?</span><span class="token punctuation">.</span>Name <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">"Product Detail"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>PageTitle<span class="token operator">&gt;</span>

@<span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"container mt-4"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>nav aria<span class="token operator">-</span>label<span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>ol <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/products"</span><span class="token operator">&gt;</span>Products<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item active"</span> aria<span class="token operator">-</span>current<span class="token operator">=</span><span class="token string">"page"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>ol<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>nav<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card shadow-sm"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-body"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-md-8"</span><span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>h2 <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-title"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge bg-secondary mb-3"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Category<span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-text text-muted"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Description<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>

                        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row mt-4"</span><span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Price<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"fs-4 text-primary fw-bold"</span><span class="token operator">&gt;</span>$@product<span class="token punctuation">.</span>Price<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"N2"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Stock<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge @(product.Stock &gt; 30 ? "</span>bg<span class="token operator">-</span>success<span class="token string">" : product.Stock &gt; 10 ? "</span>bg<span class="token operator">-</span>warning<span class="token string">" : "</span>bg<span class="token operator">-</span>danger<span class="token string">") fs-6"</span><span class="token operator">&gt;</span>
                                    @product<span class="token punctuation">.</span>Stock units
                                <span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-footer"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>TelerikButton OnClick<span class="token operator">=</span><span class="token string">"GoBack"</span>
                               ThemeColor<span class="token operator">=</span><span class="token string">"@ThemeConstants.Button.ThemeColor.Primary"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&amp;</span>larr<span class="token punctuation">;</span> Back to Products
                <span class="token operator">&lt;</span><span class="token operator">/</span>TelerikButton<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token punctuation">}</span>

@code <span class="token punctuation">{</span>
    <span class="token punctuation">[</span>Parameter<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">private</span> Product<span class="token operator">?</span> product<span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">+</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        product <span class="token operator">=</span> ProductService<span class="token punctuation">.</span><span class="token function">GetProductById</span><span class="token punctuation">(</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// 3.            </span>
            Navigation<span class="token punctuation">.</span><span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">HandleNotFound</span><span class="token punctuation">(</span><span class="token keyword">object</span><span class="token operator">?</span> sender<span class="token punctuation">,</span> NotFoundEventArgs e<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 4.</span>
        NotFoundContext<span class="token punctuation">.</span><span class="token function">UpdateContext</span><span class="token punctuation">(</span>
            <span class="token string">"Product Not Found"</span><span class="token punctuation">,</span>
            $<span class="token string">"The product with ID {Id} does not exist in our catalog."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">GoBack</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Navigation<span class="token punctuation">.</span><span class="token function">NavigateTo</span><span class="token punctuation">(</span><span class="token string">"/products"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 5.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">-</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Let&rsquo;s analyze in more detail what happens in the code above:</p><ol><li>We subscribe to <code>Navigation.OnNotFound</code> to monitor when a product is not found.</li><li>The product is searched by its ID.</li><li>If the product does not exist, <code>Navigation.NotFound</code> is invoked, allowing the rendering of the component with an HTTP 404 code.</li><li>In the <code>HandleNotFound</code> handler, we set a custom title and message.</li><li>We unsubscribe from the event to avoid memory leaks.</li></ol><p>With the previous implementation, if we navigate to a nonexistent URL such as https://localhost:7038/not-found-page, a generic message will be displayed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/generic-message-custom-component.png?sfvrsn=1642c57c_2" alt="Generic message displayed by the custom component" /></p><p>However, when trying to access the URL of a nonexistent product such as https://localhost:7038/product/122, we will find a custom message:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-message-custom-component.png?sfvrsn=27ee4062_2" alt="Custom message rendered through a personalized component" /></p><p>With the above, we have confirmed that it is now much easier to handle not found routes.</p><h3 id="improvements-in-url-detection-using-navlinkmatch.all">Improvements in URL Detection using NavLinkMatch.All</h3><p>Before .NET 10, when using <code>Match="NavLinkMatch.All"</code>, Blazor was very strict in detecting links. For example, suppose you had something like this in your navigation menu:</p><p><code>&lt;NavLink href="https://www.telerik.comproducts" Match="NavLinkMatch.All"&gt;Products&lt;/NavLink&gt;</code></p><p>If you visited the <code>/products</code> page, the UI would mark an active link in the menu. However, if you navigated to a different URL such as <code>/products?price=cheap</code> or <code>/products#offers</code>, the UI would mark the section as inactive, leading to a poor user experience. This behavior is currently happening in our application:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/navigation-url-change-focus-loss.gif?sfvrsn=5b397a86_2" alt="Focus loss during navigation URL change" /></p><p>In the image above, you can see how the focus is lost when navigating to a product details page. This happens because the main URL is <code>http://localhost:5084/products</code>, while the product URL looks like this: <code>http://localhost:5084/product/2</code>.</p><p>Fortunately, in .NET 10 we can inherit from <code>NavLink</code> and write custom logic to handle matching the URLs we need:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomNavLink</span> <span class="token punctuation">:</span> NavLink
<span class="token punctuation">{</span>
    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">bool</span> <span class="token function">ShouldMatch</span><span class="token punctuation">(</span><span class="token keyword">string</span> currentUriAbsolute<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        <span class="token keyword">var</span> baseUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>NavigationManagerBase<span class="token punctuation">.</span>BaseUri<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> currentUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> relativePath <span class="token operator">=</span> currentUri<span class="token punctuation">.</span>AbsolutePath<span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        <span class="token keyword">var</span> href <span class="token operator">=</span> AdditionalAttributes <span class="token operator">!=</span> <span class="token keyword">null</span>
            <span class="token operator">&amp;&amp;</span> AdditionalAttributes<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"href"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> hrefValue<span class="token punctuation">)</span>
            <span class="token operator">?</span> hrefValue<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>href<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 3.</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>href <span class="token operator">==</span> <span class="token string">"products"</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">return</span> relativePath <span class="token operator">==</span> <span class="token string">"products"</span>
                <span class="token operator">||</span> relativePath<span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span><span class="token string">"product/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>Inject<span class="token punctuation">]</span>
    <span class="token keyword">private</span> NavigationManager NavigationManagerBase <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above:</p><ol><li>We obtain the relative path from the absolute URI.</li><li>We get the href.</li><li>We perform a custom match so that <code>NavLink</code> is active both in <code>/products</code> and <code>/product/{id}</code>.</li></ol><p>For the previous implementation to work in the project, we need to swap <code>NavLink</code> for <code>CustomNavLink</code> in <code>NavMenu.razor</code>:</p><pre class=" language-xml"><code class="prism  language-xml">...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-item px-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CustomNavLink</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>products<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-bag-fill-nav-menu<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> Products
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>CustomNavLink</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
...
</code></pre><p>The result of the implementation is that the user knows at all times where they are, thanks to the implementation of a custom <code>NavLink</code>:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-navlink-logic.gif?sfvrsn=7067cad3_2" alt="Implementing custom logic for the NavLink component" /></p><h2 id="conclusion">Conclusion</h2><p>Throughout this article, you have learned about some new features in .NET 10 that are particularly useful for handling routes.</p><p>This includes creating a context for managing information on an error page and how to use this context to show users specific error pages, in addition to an inheritance of NavLink to define custom rules for better navigation.</p><p>Undoubtedly, these new features were much needed in Blazor. Now it&rsquo;s your time to use them and create better user experiences utilizing them alongside Progress Telerik <a target="_blank" href="https://www.telerik.com/blazor-ui">components for Blazor</a>.</p><img src="https://feeds.telerik.com/link/10827/17334896.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:354321ce-fa28-42ad-be8e-5ef8fe72a13c</id>
    <title type="text">Loading UI/UX Patterns for AI Applications</title>
    <summary type="text">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</summary>
    <published>2026-04-28T16:39:10Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17327238/loading-ui-ux-patterns-ai-applications"/>
    <content type="text"><![CDATA[<p><span class="featured">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</span></p><p>AI-driven applications introduce a new set of challenges for user experience design. Traditional web applications operate within predictable boundaries, where requests complete in a consistent and measurable way. AI features change that dynamic, with response times ranging from near-instant to long-running operations that can take minutes. Designing for that variability is no longer optional, it is a core part of building reliable AI-powered applications.</p><p>The challenge extends beyond implementation into perception. Users do not experience time objectively; they react to how it feels. A blank screen creates doubt, while visible progress keeps the experience grounded. When feedback aligns with the effort required, the interaction feels natural. When it does not, friction builds quickly. Thoughtful loading design addresses this gap by communicating progress, setting expectations and reinforcing the value of the task in motion.</p><p>This guide focuses on practical loading UI patterns for AI scenarios, organized by real-world wait times. Each pattern is implemented with <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor and Progress Telerik UI components</a>, keeping the discussion grounded in tools developers already use. The goal is simple: create loading experiences that do more than fill time, improving perceived performance and building confidence in the system behind the scenes.
</p><h2>Understanding AI Application Loading Patterns</h2><p>AI applications introduce a different class of loading behavior. Traditional web requests are relatively predictable, but AI workloads are not. Inference, natural language processing and computer vision all introduce variability based on model complexity, input size and system load. As powerful as these capabilities are, they require a more deliberate approach to how progress is communicated.</p><p>Effective loading experiences center on managing expectations through clear, continuous feedback. Every loading state should communicate what is happening now, what has completed and what comes next, keeping users oriented as the system works. With this foundation in place, loading patterns can be tailored to specific wait times, keeping the experience responsive and understandable regardless of how long processing takes.</p><h2>Near Instantaneous Responses (Less Than 1 Second)</h2><p>For interactions under one second, traditional loading indicators often add more friction than value. Quick flashes of spinners or progress bars create a visual glitch that feels broken rather than helpful. Instead, the interface should respond immediately with subtle visual feedback that confirms the action without interrupting flow.</p><p>One exception stands out: when a fast action represents the final step in a longer workflow. In these cases, introducing a brief, intentional delay can create a sense of completion, giving users a moment to recognize the result of their effort before moving on.</p><h3>AI Task Examples</h3><p>AI tasks that typically complete in under one second include:</p><ul><li>Real-time text suggestions and autocomplete</li><li>Simple sentiment analysis on short text</li><li>Basic image classification with lightweight models</li><li>Cached AI responses</li><li>Rule-based chatbot responses</li><li>Simple recommendation filtering</li></ul><h3>Blazor Implementation Strategy</h3><p>Implementing instantaneous feedback in Blazor requires careful coordination between state changes and visual cues. The goal is to reflect user actions immediately while avoiding flicker from overly brief loading states. In practice, this often means enforcing a minimal display duration for indicators so that interactions feel smooth and intentional. </p><p>Try the A/B samples below by clicking the &ldquo;Zoom and Enhance&rdquo; buttons twice. The A sample should feel smoother on short intervals because of the intentional delay.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mAkScmvQ58rIAkuM38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Instant interactions benefit from confirmation rather than explanation. Subtle cues like button state changes, input highlighting or lightweight animations signal that the system has responded, keeping the experience fluid and uninterrupted. When these signals are consistent and immediate, users stay oriented without feeling like they are waiting.</p><h2>Short Wait Times (1&ndash;3 Seconds)</h2><p>Short waits call for lightweight, indeterminate indicators that acknowledge progress without interrupting flow. Simple spinners or skeleton screens work well because they provide immediate feedback while keeping the interface responsive. More elaborate animations tend to work against this, as users do not have enough time to process them and the interaction can feel slower than it is.</p><p>Skeleton screens are especially effective in AI scenarios because they establish structure upfront. By rendering a placeholder version of the final layout, the interface maintains continuity and gives users a clear sense of what is coming next as content is generated.</p><h3>AI Task Examples</h3><p>AI operations in the 1&ndash;3 second range include:</p><ul><li>Text generation for short responses</li><li>Image enhancement or basic filtering</li><li>Summarizing a document of moderate length</li><li>Translation of short to medium text</li><li>Basic data analysis and insights generation</li><li>Voice-to-text conversion for brief audio</li></ul><h3>Blazor Implementation Strategy</h3><h4>Chat Progress and Thinking</h4><p>The Telerik UI for&nbsp;<a href="https://www.telerik.com/blazor-ui/skeleton" target="_blank">Blazor Skeleton</a> component provides a straightforward way to mirror final layouts while AI-generated content is loading and the agent is thinking.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/wqkymwFI34Q3Bj4H27?editor=true&amp;result=true&amp;errorList=false"></iframe><h4>Smart Paste Loading</h4><p>The following demo shows a <a href="https://www.telerik.com/blazor-ui/smartpastebutton" target="_blank">smart paste interaction</a> that incorporates loading indicators to signal the process taking place. The Loading Container is displayed over the form fields being populated, while a button loader indicates the action is taking place once the button is clicked.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mquemabj125i6dVw38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Short waits benefit from clarity without added friction. Contextual messaging that reflects the task in progress, such as &ldquo;Thinking&rdquo; or &ldquo;Generating recommendations,&rdquo; helps users understand what is happening without slowing the experience down. When combined with progressive disclosure, where partial results are rendered as they become available, the interface stays active and responsive, reducing perceived wait time while making the system&rsquo;s work visible.</p><h2>Medium Wait Times (3&ndash;10 Seconds)</h2><p>Users begin to question responsiveness, and a lack of visible progress quickly turns into doubt. Multi-agent workflows or agentic workflows with multiple indeterminate steps can be difficult to navigate. Agents can make requests to external tools that have asynchronous processes. While these processes run, it is important to report the activity in a manageable way without losing focus of the main content.</p><h3>Blazor Implementation</h3><p>Provide feedback for AI operations that expose measurable progress. The example below shows a chat process where the agent uses external tools. Loading indicators are exposed in a collapsible panel to allow the user to see the progress but also hide it when the details are no longer necessary.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QqEoGwvA09jzuqBe29?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Medium waits benefit from a balance of clarity and momentum. Directional time estimates, even something as simple as &ldquo;This may take about a minute,&rdquo; help users decide whether to stay engaged or return later, while continuously moving progress indicators reinforce that the system is still working. When paired with contextual messaging that explains the current step or highlights what is being processed, the experience shifts from passive waiting to a guided interaction that keeps users oriented and informed.</p><h2>Extended Wait Times (10+ Seconds)</h2><p>Extended wait times require a different approach. Clear visibility into progress matters, but so does the freedom to move on without losing track of what is happening. Combining progress indicators with background processing patterns allows the system to stay transparent while keeping the rest of the application usable.</p><p>Percent-complete indicators play a central role here by giving users a sense of scale. Seeing measurable progress helps them judge how much work remains and whether it is worth continuing to wait. At the same time, providing cancel or abort options keeps users in control rather than locked into a long-running process.</p><p>Step-based indicators work especially well for multi-stage AI operations by breaking the process into clear, understandable phases. Even when exact timing is uncertain, each completed step provides direction and reinforces forward movement.</p><h3>Blazor Implementation</h3><p>The following example uses a <a href="https://www.telerik.com/blazor-ui/grid" target="_blank">grid</a> format to display and manage agent activity. Each element displays important information about the task&rsquo;s state. In addition, failure modes are included and made actionable from the interface. This type of UX is great for dashboard-like scenarios where users can launch agent workflows and return later to see the task completion status.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QKOSGwlH59MIvvBw27?editor=true&amp;result=true&amp;errorList=false"></iframe><p>If the user may encounter a long and undetermined loading state, a looping indicator can be used to communicate the overall steps that are running in the background, without labeling exactly what stage the process is at now. This nondeterministic progress indicator displays a carousel of information while the user waits. </p><p>With agentic applications, long processes can give the impression of wasted time, even though a process that takes minutes for an agent may have taken a human hour to complete manually. This loading pattern reminds users of the work going on behind the scenes.&nbsp;While some consider this a &ldquo;dark pattern&rdquo; when used to obscure the process details, it can be quite useful when used correctly to share honest information about the value the process provides.</p><p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/GKkImcbe33AOXWRF16?editor=true&amp;result=true&amp;errorList=false">&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;</iframe></p><p>Extended waits benefit from a design that prioritizes flexibility and user control. Background task patterns, such as persistent status panels or drawers, allow users to continue working while keeping progress visible, reducing the friction of being forced to wait in place. When paired with notification systems that signal completion, the need to actively monitor progress is removed entirely, giving users confidence that the system will follow through.</p><p>Longer waits create space to surface meaningful insights, explain processing steps or provide guidance for improving future results. When these elements are combined effectively, the experience shifts from a blocking delay to a managed, transparent process that keeps users informed without interrupting their workflow.</p><h2>Advanced Patterns for AI Applications</h2><h3>Contextual Loading Messages</h3><p>AI applications benefit from contextual loading messages that explain current processing steps. Replace generic &ldquo;Loading...&rdquo; text with specific descriptions: &ldquo;Analyzing image composition,&rdquo; &ldquo;Generating creative variations&rdquo; or &ldquo;Cross-referencing knowledge base.&rdquo; These messages build user understanding of AI capabilities while setting realistic expectations for output quality.</p><h3>Streaming and Incremental Results</h3><p>Many modern AI applications support streaming responses, particularly for text generation. Implement progressive disclosure patterns that display AI outputs as they generate, reducing perceived wait time while demonstrating active processing. This approach works particularly well for conversational AI interfaces and content-generation tools.</p><h2>Summary</h2><p>Designing loading experiences for AI applications is no longer about handling delays. It is about shaping how users understand and interact with the system. When feedback aligns with the work being performed, users stay engaged and in control regardless of how long processing takes.</p><p>By matching loading strategies to expected wait times, developers can move beyond generic indicators and deliver feedback that is intentional and informative. From immediate visual confirmation to multi-stage progress tracking, each pattern helps make AI interactions feel predictable and responsive.</p><p>Blazor and Telerik UI components provide a practical foundation for implementing these patterns, enabling teams to build consistent loading experiences without introducing unnecessary complexity. The advantage comes from how these tools are applied, not just their availability.</p><p>As AI continues to shape modern applications, loading design becomes a defining part of the user experience. Applications that communicate clearly during processing will feel faster and more reliable, regardless of the work happening behind the scenes.</p><h3>Get Started with These Examples</h3><p>Use any of the Telerik UI for <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor components</a> shown above, plus the <a target="_blank" href="https://www.telerik.com/blazor-mcp-servers">Blazor MCP servers</a> free with the 30-day trial.</p><p><a href="https://www.telerik.com/try/ui-for-blazor" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17327238.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:89e0e3fc-f615-4718-b937-f4de1ceba07a</id>
    <title type="text">Introducing the Progress Agentic RAG .NET SDK</title>
    <summary type="text">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</summary>
    <published>2026-04-23T15:33:21Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17324229/introducing-progress-agentic-rag-net-sdk"/>
    <content type="text"><![CDATA[<p><span class="featured">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</span></p><h2>What Is Progress Agentic RAG?</h2><p>Progress Agentic RAG is a RAG-as-a-Service that makes it dramatically easier to build AI systems grounded in real, trusted content. Rather than wiring together vector databases, embedding pipelines and retrieval logic yourself, Progress Agentic RAG provides an end-to-end platform for indexing, understanding and retrieving multimodal data.</p><p>It enables intelligent, agent-driven workflows that combine structured knowledge, contextual search and LLM orchestration into a unified experience.</p><p>With the introduction of the .NET SDK for Progress Agentic RAG, .NET developers can integrate this capability directly into their applications with just a few lines of code, leveraging modern .NET architecture patterns such as dependency injection, async workflows and strongly typed APIs.</p><h3>From Retrieval to Agentic Intelligence</h3><p>Progress Agentic RAG offers a fully capable AI Search Dashboard solution that allows you to get started in a matter of minutes. Simply connect or upload your data to a Knowledge Box, wait for NucliaDB&rsquo;s blazing-fast indexing engine to process it, and you&rsquo;re ready to explore grounded, contextual AI responses.</p><p>That immediate productivity is powerful. But modern enterprise development requires more than a dashboard.</p><p>Enterprise environments demand typed APIs, strong tooling and predictable behavior. Many RAG solutions are Python-first, leaving .NET teams stitching together REST calls manually and building custom abstractions just to regain the ergonomics they expect from their platform.</p><p>The new .NET SDK changes that.</p><p>It provides:</p><ul style="margin-top:0in;" type="disc"><li>Strongly typed APIs</li><li>Async-first patterns</li><li>Native .NET integration</li><li>Simplified knowledge base interaction</li></ul><p>With these capabilities, you can move beyond simple retrieval and begin composing intelligent, agent-driven experiences directly inside your application architecture. As powerful as it is convenient, the .NET SDK makes a great choice for new AI-enabled .NET applications.</p><h2>Getting Started</h2><p>Once a Knowledge Box has been established, you can begin interacting with Progress Agentic RAG through the .NET SDK.</p><p>The SDK is distributed as a <a target="_blank" href="https://www.nuget.org/packages/Progress.Nuclia">NuGet package</a> and covers the complete NucliaDB REST API. That includes strongly typed models, structured output helpers, dependency injection extensions and more than 200 APIs that expose the full surface area of the platform. See the <a target="_blank" href="https://docs.rag.progress.cloud/docs/develop/dotnet-sdk/">SDK documentation page </a>for a comprehensive list of service providers available.</p><h3>Install the NuGet Package</h3><pre><code class="language-csharp">dotnet add package Progress.Nuclia</code></pre><p>With the package installed, you can register the INucliaDb interface using modern dependency injection patterns. The SDK supports everything from basic configuration to advanced multi-tenant scenarios using keyed services.</p><h3>Register the Client</h3><pre><code class="language-csharp">using Progress.Nuclia.Extensions;

// Create configuration
var config = new NucliaDbConfig(
    ZoneId: "aws-us-east-2-1",
    KnowledgeBoxId: "your-knowledge-box-id",
    ApiKey: "your-api-key"
);

// Register with logging
builder.Services.AddNucliaDb(config).UseLogging();</code></pre><p>This approach aligns naturally with ASP.NET Core&rsquo;s architecture. You configure once, inject where needed, and keep your AI integration cleanly separated from business logic.</p><h2>Ask Questions with Agentic RAG</h2><p>With configuration complete, you can begin querying your Knowledge Box using AskAsync or AskStreamingAsync.</p><pre><code class="language-csharp">// Make request
AskRequest askRequest = new("What issues are driving the most customer escalations this quarter?");
var response = await client.Search.AskAsync(askRequest);

// Display answer
Console.WriteLine(response.Data.Answer);</code></pre><p>In just a few lines of code, you&rsquo;re executing a grounded, agent-driven query against indexed enterprise data.</p><p>The Ask functionality is only the beginning. With more than 200 APIs available in the SDK, you can ingest and manage resources, create conversational interactions and perform search, all using strongly typed, async-first C# patterns.</p><p>With structured configuration and native .NET integration in place, you can move from experimentation to production-ready AI systems with confidence.</p><h2>Explore the Examples: Blazor and .NET MAUI</h2><p>The fastest way to understand what Progress Agentic RAG can do in a real application is to see it running inside the frameworks you already use.</p><p>We&rsquo;ve published hands-on examples built with:</p><ul style="margin-top:0in;" type="disc"><li><a target="_blank" href="https://github.com/telerik/telerik-blazor-progress-rag-demo">Blazor &mdash; showcasing grounded AI experiences in modern web applications</a></li><li><a target="_blank" href="https://github.com/telerik/telerik-maui-progress-rag-demo">NET MAUI &mdash; demonstrating cross-platform, AI-powered mobile and desktop apps</a></li></ul><p>These samples go beyond simple API calls. They show how to:</p><ul style="margin-top:0in;" type="disc"><li>Register the SDK using dependency injection</li><li>Execute agentic queries with structured output</li><li>Stream responses into interactive UI components</li><li>Keep AI concerns cleanly separated from presentation logic</li></ul><p>If you&rsquo;re building internal tools, customer-facing dashboards or cross-platform AI assistants, these examples provide a production-oriented starting point.</p><p>Clone the samples, wire up your Knowledge Box and see how quickly you can integrate grounded, agent-driven intelligence into your existing .NET architecture.</p><h2>Additional Media</h2><p>Learn more about Progress Agentic RAG and the .NET SDK from video tutorials and podcasts:</p><ul><li>The Progress Agentic RAG .NET SDK was featured on episode 1997 of .NET Rocks:&nbsp;<a target="_blank" href="https://www.dotnetrocks.com/details/1997">.NET Rocks! Agentic RAG with Ed Charbeneau</a></li><li>Getting started videos by Jeff (Csharp) Fritz</li></ul><div sf-youtube-url="https://youtu.be/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG" width="640" height="360" src="https://www.youtube.com/embed/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=3&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div><div sf-youtube-url="https://youtu.be/qE6zy75btLo?si=ZferEV00m-sRamDA"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG Part 2" width="640" height="360" src="https://www.youtube.com/embed/qE6zy75btLo?si=ZferEV00m-sRamDA&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=2&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div><img src="https://feeds.telerik.com/link/10827/17324229.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e67bd1e0-6dce-4ba4-92de-7c7975a18492</id>
    <title type="text">Blazor Basics: Getting Started with Blazor Development in VS Code</title>
    <summary type="text">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</summary>
    <published>2026-04-21T16:22:45Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Claudio Bernasconi </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17323030/blazor-basics-getting-started-blazor-development-vs-code"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</span></p><p>While many developers using Windows love the fully integrated development experience Visual Studio provides, it&rsquo;s not an option for some of us.</p><p>We will learn how to set up Visual Studio Code for Blazor web application development, a few first-hand tips and how to conveniently debug.</p><h2 id="why-use-visual-studio-code-for-blazor-web-development">Why Use Visual Studio Code for Blazor Web Development?</h2><p>Maybe you or your team already use <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> for software development because of its excellent code editor or GIT integration. Why learn something new if you are happy with what you already have?</p><p>Another reason that comes to mind is your preferred operating system.</p><p><a target="_blank" href="https://visualstudio.microsoft.com/">Visual Studio</a> is only available for <strong>Windows</strong>. While I am a Windows user and have always loved the fully fledged feature set provided by Visual Studio for .NET development, I understand that there are other options.</p><p>For <strong>macOS</strong> or <strong>Linux</strong>, you do not have the option to use Visual Studio. Visual Studio Code (VS Code) is a great, lightweight, and free alternative.</p><p>Also, licensing can be an issue. While the Visual Studio Community edition has limitations, VS Code is free for .NET development, both personal and commercial.</p><p>Last but not least, VS Code uses less RAM, hard-drive space and CPU compared to fully fledged IDEs, such as Visual Studio. For hardware constraint situations, it&rsquo;s an ideal choice.</p><h2 id="getting-started-the-setup">Getting Started: The Setup</h2><p>We need two essential things before we can start developing Blazor web applications with VS Code.</p><ol><li>Obviously, we need the latest <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> version installed</li><li>We also need the <a target="_blank" href="https://dotnet.microsoft.com/">latest .NET SDK</a> installed</li></ol><p>Make sure you can execute the <code>dotnet --version</code> command in a terminal. If it doesn&rsquo;t show the version of the installed <strong>.NET SDK</strong>, add the <strong>.NET CLI</strong> to the <code>PATH</code> variable of your operating system and restart your computer before trying again.</p><h2 id="installing-the-c-dev-kit">Installing the C# Dev Kit</h2><p>Next, we want to install the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit">C# Dev Kit extension</a> for Visual Studio Code.</p><p><img title="C# Dev Kit Visual Studio Code extension" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/csharp-dev-kit-extension.png?sfvrsn=55b57e85_2" alt="The marketplace page of the C# Dev Kit Visual Studio Code extension." /></p><p>This extension adds C# development features, including project and solution management, debugging, syntax highlighting, code navigation, refactorings and more, to VS Code. It&rsquo;s a mature extension with more than 13 million downloads.</p><blockquote><p><strong>Hint:</strong> The C# Dev Kit extension not only adds support for Blazor web development but for all C# project types, including desktop, mobile, web, or cloud-native applications.</p></blockquote><h2 id="creating-a-blazor-web-app-in-vs-code">Creating a Blazor Web App in VS Code</h2><p>If you open an empty VS Code instance, you&rsquo;ll get the Create .NET Project menu option in the Explorer panel.</p><p><img title="Visual Studio Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/explorer-create-new-dotnet-project.png?sfvrsn=7487329a_2" alt="The Explorer panel in Visual Studio Code in an empty project shows the Create .NET Project button." /></p><p>Pressing the <strong>Create .NET Project</strong> button will execute a VS Code command that allows you to select the project type. We select the default <strong>Blazor Web App</strong> project template and choose the destination folder on the hard drive.</p><p>Depending on the project template you choose, you&rsquo;ll be able to add additional information to influence the outcome of the created project.</p><p>After a few seconds, the Explorer now shows the project folder containing the created solution file, the project file and the <code>Components</code> folder containing the example pages and layouts.</p><p><img title="VS Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/visual-studio-code-solution-explorer.png?sfvrsn=72c36ed7_2" alt="The Explorer panel in VS Code in a Blazor Web App shows the folders, such as the Components folder and all the required code files." /></p><blockquote><p><strong>Hint:</strong> We won&rsquo;t explore the structure and architecture of the Blazor Web application.</p></blockquote><p>Another option for creating a .NET project is using the .NET CLI from the terminal. The <code>dotnet new blazor</code> command creates the same project as shown above.</p><h2 id="running-the-blazor-web-application-in-vs-code">Running the Blazor Web Application in VS Code</h2><p>Running a Blazor Web Application is no different from running any other .NET-based application. In a terminal, you can use the <code>dotnet run</code> or <code>dotnet watch run</code> command to run it.</p><p>The <code>dotnet watch run</code> command automatically rebuilds and refreshes the browser (hot reload) when you edit the code and save the file.</p><p><img title="VS Code: Running a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/dotnet-watch-run-command.png?sfvrsn=94413184_2" alt="A terminal executing the dotnet watch run command in the folder of a Blazor Web App." /></p><p>The application runs the same whether you run it in Visual Studio or from the command line without VS Code. All three options use the .NET CLI under the hood.</p><p>Compared to Visual Studio, VS Code is noticeably faster at applying simple changes to components, such as the text of the default Home page component.</p><blockquote><p><strong>Tip:</strong> Using hot reload saves a lot of time. Use the <code>dotnet watch run</code> command and open the <code>Counter.razor</code> component. Change the code in the <code>IncrementCount</code> method from <code>currentCount++</code> to <code>currentCount+=2</code> and save the file. Whenever you hit the <strong>Click Me</strong> button, the counter now increases the value by <strong>two</strong> instead of <strong>one</strong>.</p></blockquote><h2 id="debugging-c-code-in-vs-code">Debugging C# Code in VS Code</h2><p>VS Code not only supports running the Blazor web app, but also debugging it.</p><p>To enable debugging:</p><ol><li>Open the <strong>Run and Debug</strong> panel (CTRL+SHIFT+D)</li><li>Click the <strong>Run and Debug</strong> button.</li><li>Select <strong>C#</strong></li><li>Select one of the available project options, or <strong>launch the Startup</strong> project.</li><li>Click on <strong>Start Debugging</strong> (Green Triangle), or press <strong>F5</strong>.</li></ol><p>You can click left of any C# code line to add a breakpoint. A yellow background highlights the code line when the debugger stops the execution. And you can use the toolbar to step through your code and hover over variables to see their values.</p><p><img title="VS Code: Debugging a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/debugging-blazor-web-app.png?sfvrsn=dfa894d3_2" alt="The VS Code debugger stopping at a code line and showing the local variables and the toolbar to control the program execution." /></p><p>The debugging experience is comparable to that of other fully fledged IDEs and allows you to find bugs and understand code execution.</p><p>You can add variables to the watch view to keep their values visible as you step through program execution. You also see the call stack whenever the program execution halts, and you can enable or disable the breakpoints.</p><blockquote><p><strong>Hint:</strong> Make sure the application isn&rsquo;t running from a terminal, such as using the dotnet watch run command.</p></blockquote><h2 id="conclusion">Conclusion</h2><p>While I personally prefer using Visual Studio 2026 for most of my development work, I hopefully was able to have shown that Visual Studio Code also offers a first-class development environment.</p><p>For Linux or macOS, VS Code would definitely be my first choice because it provides most of the tools I regularly use and contains much less bloat than a fully fledged IDE. VS Code is free for personal and commercial use.</p><p>Depending on the scope of your project and how you want to work, VS Code is a good choice to get started with Blazor web development. Especially if you already use VS Code for other projects, such as React or Angular web application development.</p><p>If you want to learn more about Blazor development, watch my <a target="_blank" href="https://www.youtube.com/playlist?list=PLwISgxnkpZGL_LhTQCWwp-WCzupv7lcp0">free Blazor Crash Course</a> on YouTube. And stay tuned to the Telerik blog for more <a target="_blank" href="https://www.telerik.com/blogs/tag/blazor-basics">Blazor Basics</a>.</p><img src="https://feeds.telerik.com/link/10827/17323030.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:00f2f87e-a41a-4c28-8ad3-079bcbc8bc0a</id>
    <title type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows</title>
    <summary type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows.</summary>
    <published>2026-04-21T16:17:26Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17323031/angular-grid-scale-how-kendo-ui-handles-millions-rows"/>
    <content type="text"><![CDATA[<p>Let&rsquo;s say the Monday after the Super Bowl, your boss wants to see a dashboard list of the more than 120 million people who watched it. (The reality of this situation does not matter. Just go with it!) Your boss says, &ldquo;The dashboard needs to show every connected viewer in real time. A grid. Sortable. Filterable. And it can&rsquo;t be slow.&rdquo;</p><p>You open your code editor, create an HTML table, and try to render one million rows. The browser freezes. The tab crashes. Your Monday just got worse.</p><p>You need to display large amounts of data in a grid without killing the browser. The trick is simple: don&rsquo;t render what the user can&rsquo;t see.</p><p>This is where <strong>virtual scrolling</strong> comes in. Instead of creating a DOM element for every single row, you only render the rows visible on screen, plus a few extra. The user scrolls smoothly through millions of records, each new batch loading in as needed, and the browser stays fast and responsive.</p><p>Let&rsquo;s do it together! Today, we will build a Super Bowl Dashboard in Angular. We will see how to handle one million viewers using Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/">Angular Grid</a>. We will start with a simple table, see why it fails and then fix it using virtual scrolling and server-side data fetching.</p><p>Let&rsquo;s make this work in a real project.</p><h2 id="setting-up-the-project">Setting Up the Project</h2><p>First, create a new Angular application by running the command:</p><pre class=" language-bash"><code class="prism  language-bash">ng new superbowl-dashboard
</code></pre><p>Navigate to the project:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">cd</span> superbowl-dashboard
</code></pre><p>Now install the Angular Grid. The <code>ng add</code> command handles dependencies and theme configuration for us:</p><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-grid --skip-confirmation
</code></pre><p>This installs the grid package and its peer dependencies and sets up the Kendo UI default theme.</p><p>Once the installation is done, we need to activate our Kendo UI license. This step removes the watermark and unlocks all the features.</p><blockquote><p>Don&rsquo;t have a license? Don&rsquo;t worry! You can get a <a target="_blank" href="https://www.telerik.com/try/kendo-ui">completely free trial</a> with <strong>no credit card required</strong>, so you can follow along and build the dashboard without worrying about the watermark.</p></blockquote><p>To do this, download the license key file from your Telerik account. Then, run this command inside your project folder:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>Because the Kendo UI Grid is so robust, it includes many features that can slightly increase the initial bundle size. We need to prevent Angular from throwing a &ldquo;bundle initial exceeded maximum budget&rdquo; error when we run or build an app. Open the <code>angular.json</code> file and increase the <code>budgets</code> config for the <code>initial</code> type to around <code>5MB</code> for warnings and <code>10MB</code> for errors.</p><p>Perfect! We have a fresh Angular project with Kendo UI Grid installed and styled. Now let&rsquo;s generate our Super Bowl viewers&rsquo; fake data.</p><h2 id="generating-one-million-viewers">Generating One Million Viewers</h2><p>Before we start with the grid, we need data. We&rsquo;ll create a single service that handles everything: generating fake viewer data for client-side demos and simulating a paginated server API for server-side scrolling.</p><p>To create the service, open the terminal and run the following Angular CLI command:</p><pre class=" language-bash"><code class="prism  language-bash">ng g s services/viewer
</code></pre><p>First, we will define a simple <code>Viewer</code> interface so our grid knows what fields to expect. Then, our service will include a <code>generateViewers</code> method to create mock data, and a <code>fetchPage</code> method that simulates a paginated server API. We won&rsquo;t use <code>fetchPage</code> right away, but it will be ready for when we implement server-side virtual scrolling later.</p><p>Don&rsquo;t worry too much about the exact implementation of the helper methods; the main idea here is not how to generate fake users, but how to create a grid capable of supporting massive amounts of data without breaking a sweat!</p><p>Open <code>src/app/services/viewer.ts</code> and add the following:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Viewer</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  username<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  isLive<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">PagedResult</span> <span class="token punctuation">{</span>
  data<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> ADJECTIVES <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Swift"</span><span class="token punctuation">,</span>
  <span class="token string">"Lucky"</span><span class="token punctuation">,</span>
  <span class="token string">"Bold"</span><span class="token punctuation">,</span>
  <span class="token string">"Chill"</span><span class="token punctuation">,</span>
  <span class="token string">"Epic"</span><span class="token punctuation">,</span>
  <span class="token string">"Happy"</span><span class="token punctuation">,</span>
  <span class="token string">"Lazy"</span><span class="token punctuation">,</span>
  <span class="token string">"Wild"</span><span class="token punctuation">,</span>
  <span class="token string">"Cool"</span><span class="token punctuation">,</span>
  <span class="token string">"Fast"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> NOUNS <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Fan"</span><span class="token punctuation">,</span>
  <span class="token string">"Eagle"</span><span class="token punctuation">,</span>
  <span class="token string">"Tiger"</span><span class="token punctuation">,</span>
  <span class="token string">"Bear"</span><span class="token punctuation">,</span>
  <span class="token string">"Wolf"</span><span class="token punctuation">,</span>
  <span class="token string">"Hawk"</span><span class="token punctuation">,</span>
  <span class="token string">"Fox"</span><span class="token punctuation">,</span>
  <span class="token string">"Lion"</span><span class="token punctuation">,</span>
  <span class="token string">"Shark"</span><span class="token punctuation">,</span>
  <span class="token string">"Bull"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">"root"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ViewerService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly TOTAL_VIEWERS <span class="token operator">=</span> 1_000_000<span class="token punctuation">;</span>

  <span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> startId <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> viewers<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> count<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      viewers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> startId <span class="token operator">+</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>
        username<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>ADJECTIVES<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>NOUNS<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
        watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">240</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        isLive<span class="token punctuation">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">0.08</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> viewers<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/**
   * Simulates a server API call with pagination.
   * Uses a small delay to mimic real network latency.
   */</span>
  <span class="token keyword">async</span> <span class="token function">fetchPage</span><span class="token punctuation">(</span>skip<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> take<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>PagedResult<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      data<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>take<span class="token punctuation">,</span> skip<span class="token punctuation">)</span><span class="token punctuation">,</span>
      total<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>TOTAL_VIEWERS<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> randomFrom<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token punctuation">(</span>arr<span class="token punctuation">:</span> T<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> T <span class="token punctuation">{</span>
    <span class="token keyword">return</span> arr<span class="token punctuation">[</span>Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">randomBetween</span><span class="token punctuation">(</span>min<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> max<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>max <span class="token operator">-</span> min <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> min<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Each viewer has a fun auto-generated username (like <code>SwiftEagle4821</code>), their watch time and a live status. The <code>fetchPage</code> method is an <code>async</code> function that simulates a paginated API with a small delay to mimic network latency. We&rsquo;ll use <code>generateViewers</code> first and come back to <code>fetchPage</code> later.</p><p>With our service ready, let&rsquo;s see what happens when we try to render all these viewers at once.</p><h2 id="the-problem-rendering-all-rows-at-once">The Problem: Rendering All Rows at Once</h2><p>Let&rsquo;s try to do it and experience the problem firsthand. Open <code>src/app/app.ts</code> and replace its content:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/common"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s open our template file, <code>src/app/app.html</code> and add a few buttons for 1K, 10K and 100K viewers. These buttons will call our <code>loadViewers()</code> method to quickly generate different amounts of fake data.</p><p>Finally, we&rsquo;ll use the <code>&lt;kendo-grid&gt;</code> component in our template. The key property to pay attention to is <code>[data]="viewers()"</code>, which tells Kendo UI to read from our reactive signal and constantly display the list of viewers.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>grid-container<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">600</span>px<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span> <span class="token attr-name">border</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">100%</span><span class="token punctuation">;</span> <span class="token property">border-collapse</span><span class="token punctuation">:</span> collapse<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>#<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Username<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Watch (min)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Live<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">&gt;</span></span>
      @for (viewer of viewers(); track viewer.id) {
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.id }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.username }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.watchTimeMin }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.isLive ? 'Yes' : 'No' }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
      }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Now we can run our app. Go to the terminal and execute the following command:</p><pre class=" language-bash"><code class="prism  language-bash">ng serve
</code></pre><p>This will start the local development server. Once it finishes compiling, open your browser and navigate to <code>http://localhost:4200</code>.</p><p>Click <strong>&ldquo;1K Viewers&rdquo;</strong> and it feels smooth. Click <strong>&ldquo;10K Viewers&rdquo;</strong> and you will notice a significant lag.</p><p>Now click <strong>&ldquo;100K Viewers&rdquo;</strong> &hellip; and watch the browser scream for help. The page will freeze, the scroll will be jumpy and you might even get the &ldquo;Page Unresponsive&rdquo; dialog.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/page-unresponsive-dialog.png?sfvrsn=bb5cb2ab_2" alt="Page Unresponsive dialog" /></p><p>We have seen the problem: standard HTML tables and simple loops cannot handle huge datasets. When you click the 1M Viewers button, the browser stops working.</p><p>OK, but how can we fix this?</p><h2 id="the-solution-angular-grid-withvirtual-scrolling">The Solution: Angular Grid withVirtual Scrolling</h2><p>If you read about Kendo UI, you know the Kendo UI Grid, but before we to start to use it, I want to explain &ldquo;virtual scrolling.&rdquo; Think of virtual scrolling like a camera moving over a big stadium. You only see the seats in the camera frame, maybe 50 seats. The stadium has 1,000,000 seats, but the camera doesn&rsquo;t need to show all of them at once. It only shows what is visible.</p><p>Kendo UI Grid does exactly this with one property: <code>scrollable="virtual"</code>. Let&rsquo;s use it.</p><p>First, update your <code>app.ts</code>. We need to import <code>KENDO_GRID</code> and remove the <code>CommonModule</code> because the grid will handle everything now:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s update <code>src/app/app.html</code>. We will replace the standard <code>&lt;table&gt;</code> with the <code>&lt;kendo-grid&gt;</code> component:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
  <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span>
  <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span>
  <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>virtual<span class="token punctuation">"</span></span>
  <span class="token attr-name">[rowHeight]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>36<span class="token punctuation">"</span></span>
  <span class="token attr-name">[pageSize]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>50<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="why-does-this-work">Why Does This Work?</h3><p>We added three important properties to the <code>&lt;kendo-grid&gt;</code> to make it fast:</p><ol><li><strong><code>scrollable="virtual"</code></strong>: This tells Kendo UI: &ldquo;Don&rsquo;t create all the rows at once. Only create the ones the user can see right now.&rdquo;</li><li><strong><code>[rowHeight]="36"</code></strong>: The grid needs to know exactly how tall each row is. This helps the grid calculate the scroll position correctly.</li><li><strong><code>[pageSize]="50"</code></strong>: This is how many rows Kendo UI keeps in the DOM. A good tip: set this to <strong>3 times</strong> the number of rows visible on your screen.</li></ol><p>Now, if you click the buttons, you will see that a page with 10,000, 100,000 or even <strong>one million viewers</strong> scrolls smoothly. The browser is fast because only ~50 rows are in the DOM at any time.</p><p>Kendo UI Grid does all the hard work for the rendering.</p><p>But there is one more problem. We fixed the <strong>rendering</strong>, but we are still loading one million records into the browser&rsquo;s memory. For a real app, loading 1,000,000 records at once is a bad idea. It uses too much RAM and it is slow to start.</p><p>What if we want to show one million users in real-time without using all the RAM? Let&rsquo;s use the final solution: <strong>Server-Side Data Fetching</strong>.</p><h2 id="server-side-data-fetching-with-endless-scrolling">Server-Side Data Fetching with Endless Scrolling</h2><p>Here&rsquo;s the reality: the Super Bowl has <strong>120 million viewers</strong>. We can&rsquo;t load all of them into the browser at once. Instead, the grid should fetch data page by page as the user scrolls , loading only what&rsquo;s needed, when it&rsquo;s needed.</p><p>Kendo UI Grid supports this out of the box with the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/api/gridcomponent#scrollbottom"><code>scrollBottom</code></a> event. When the user scrolls to the bottom of the current data, the grid fires this event, and we simply fetch the next page and append it. Let&rsquo;s build it!</p><p>Generate a new component by running the command in the terminal:</p><pre class=" language-bash"><code class="prism  language-bash">ng g c components/live-grid
</code></pre><p>Open <code>src/app/components/live-grid/live-grid.ts</code> and replace the content with:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> ChangeDetectionStrategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"../../services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-live-grid"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./live-grid.html"</span><span class="token punctuation">,</span>
  changeDetection<span class="token punctuation">:</span> ChangeDetectionStrategy<span class="token punctuation">.</span>OnPush<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LiveGrid</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  isConnected <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  loading <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  pageSize <span class="token operator">=</span> 1_000<span class="token punctuation">;</span>

  <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>isConnected<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">onScrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loading</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">fetchPage</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">viewers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>current<span class="token punctuation">,</span> <span class="token operator">...</span>result<span class="token punctuation">.</span>data<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The logic here is straightforward. First, <code>connect()</code> starts the process by fetching the initial data page using <code>loadMore()</code>. Then, whenever the user scrolls to the bottom of the grid, the <code>(scrollBottom)</code> event fires, triggering <code>onScrollBottom()</code> to fetch and append the next chunk of data to our <code>viewers</code> signal.</p><p>To make this work in the UI, we just need to set <code>scrollable="scrollable"</code> on our Kendo Grid to enable endless scrolling, and bind our <code>loading</code> signal to show a skeleton animation during the fetch.</p><p>Now let&rsquo;s build the template in <code>src/app/components/live-grid/live-grid.html</code> to tie all of this together:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Live Server Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Viewers: {{ viewers().length }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>connect()<span class="token punctuation">"</span></span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isConnected()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Connect to Live Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

@if (isConnected()) {
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> 
    <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span> 
    <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> 
    <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>scrollable<span class="token punctuation">"</span></span>
    <span class="token attr-name">[loading]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loading()<span class="token punctuation">"</span></span>
    <span class="token attr-name">(scrollBottom)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onScrollBottom()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>Wire the component into the app. Update <code>app.ts</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> LiveGrid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./components/live-grid/live-grid"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>LiveGrid<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>And <code>src/app/app.html</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-live-grid</span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>Run <code>ng serve</code>, open <code>http://localhost:4200</code> and click &ldquo;Connect to Live Feed.&rdquo;</p><p>You&rsquo;ll see the grid load the first 1,000 viewers. Now scroll down. When you reach the bottom, the grid fetches the next 1,000 viewers and appends them and the loading skeleton appears briefly while data is being fetched. Keep scrolling, and watch the viewers counter grow until it gets to 1,000,000. Yeah!!</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/grid-scroll.gif?sfvrsn=56a027cd_2" alt="Super Bowl viewers dashboard being scrolled and scrolled, very smoothly" /></p><h2 id="recap">Recap</h2><p>We saw the problem: rendering thousands of rows at once makes the browser freeze. Then we added Kendo UI <strong>virtual scrolling</strong> with <code>scrollable="virtual"</code>. This is the secret to showing large datasets without crashing.</p><p>Finally, we used <strong>server-side data fetching</strong> with the <code>(scrollBottom)</code> event. The grid loads data page by page. This keeps the memory usage low and the experience smooth.</p><p>In your next challenge don&rsquo;t worry about the data, Kendo UI Grid makes it easy. With a few properties, your dashboard can handle millions of rows. ✌</p><p>Give it a try for free!</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Kendo UI for Angular</a></p><p>Happy coding!</p><p>Source Code: <a target="_blank" href="https://github.com/danywalls/kendo-grid-large-app">https://github.com/danywalls/kendo-grid-large-app</a></p><img src="https://feeds.telerik.com/link/10827/17323031.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a21016d2-1ad2-4a02-bae7-eef7529852c5</id>
    <title type="text">What’s Next for React in 2026</title>
    <summary type="text">The State of React survey reveals developer insights into the patterns and tools they use and how their opinions about React are shifting.</summary>
    <published>2026-04-20T16:37:09Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17322394/whats-next-react-2026"/>
    <content type="text"><![CDATA[<p><span class="featured">The State of React survey reveals developer insights into the patterns and tools they use and how their opinions about React are shifting.</span></p><p>The <a target="_blank" href="https://2025.stateofreact.com/">State of React 2025</a> survey results paint an interesting picture of where React stands as it heads into 2026. <a target="_blank" href="https://www.telerik.com/blogs/whats-new-react-19">React 19</a> adoption is well underway, but stability hasn&rsquo;t translated into consensus about what comes next.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/whats-next-for-react-state-of-react-25-survey.png?sfvrsn=952363dc_2" alt="State of React 2025" /></p><p>What the survey shows is that some of React&rsquo;s biggest bets are paying off while others are still finding their footing. The patterns we&rsquo;re building with, the tools we&rsquo;re reaching for and the way we think about React architecture are all shifting. In this article, we&rsquo;ll look at what the data actually says and what it means for the year ahead.</p><h2 id="react-server-components">React Server Components</h2><p>One of the most talked about additions to React in the last couple of years has been <a target="_blank" href="https://react.dev/reference/rsc/server-components">React Server Components (RSC)</a>. Server Components are components that run on the server and let us keep server-only logic, data access and sensitive code out of the client bundle. Alongside that, <a target="_blank" href="https://react.dev/reference/rsc/server-functions">Server Functions</a> let client-side code invoke server-side logic through a framework-managed interface, without hand-rolling traditional API endpoints for every interaction.</p><blockquote><p>For a great read on Server Components, check out <a target="_blank" href="https://www.telerik.com/blogs/current-state-react-server-components-guide-perplexed">The Current State of React Server Components: A Guide for the Perplexed</a>.</p></blockquote><p>Some have said that Server Components were to be the foundation of React&rsquo;s next evolution toward a more complete full-stack framework. The <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#all_features">survey data</a>, however, is more nuanced.</p><p>About 45% of respondents have used Server Components, and among those who have, only about a third report a positive experience. Server Functions tell a similar story, with roughly 37% adoption and 33% positive sentiment among users. In both cases, only a small fraction of the overall community has used these features <em>and</em> come away with a positive sentiment.</p><p>Contrast that with <a target="_blank" href="https://react.dev/reference/react/Suspense">Suspense</a>, React&rsquo;s mechanism for declaratively handling loading states while waiting for asynchronous data or code. <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#new_apis_ratios">Suspense has the highest adoption rate among new features and boasts strong satisfaction numbers</a>.</p><p>It&rsquo;s a useful comparison because it shows that the React community isn&rsquo;t resistant to new patterns: when a new API solves a clear problem with a reasonable developer experience, adoption follows. With that said, Suspense is a smaller, more contained feature that&rsquo;s easier to introduce into existing applications, while Server Components require a more fundamental shift in how we think about our application architecture.</p><p>The architecture data reinforces this picture. <a target="_blank" href="https://2025.stateofreact.com/en-US/usage/#js_app_patterns">When asked which rendering patterns they&rsquo;ve used</a>, most teams still rely on the tried-and-true: Single-Page Applications lead the way at 84%, followed by Server-Side Rendering (61%) and Static Site Generation (44%). Newer approaches like partial hydration (25%), streaming SSR (18%) and islands architecture (14%) are gaining traction, but they&rsquo;re far from mainstream.</p><p>None of this means Server Components don&rsquo;t matter. Architecturally, the ability to move rendering logic to the server, reduce client-side JavaScript and simplify data fetching is significant. But the developer experience may just need more time to mature and catch up to the architectural promise. For most teams, the pragmatic move is to adopt RSC incrementally and where it makes sense, rather than treating it as a mandate to rewrite everything.</p><h2 id="what-developers-are-curious-about">What Developers Are Curious About</h2><p>The survey also gives us a sense of where developer curiosity is headed. The <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#reading_list">reading list</a>, the section of the survey that lets respondents flag topics they want to learn more about, is a useful signal here.</p><p><a href="https://react.dev/reference/react/ViewTransition">ViewTransition</a>, a React API for coordinating animated transitions between UI states, ranks near the top. So does <a target="_blank" href="https://react.dev/reference/react/Activity">Activity</a>, which lets us hide and show parts of our UI while preserving their internal state and DOM. Both are currently only available in React&rsquo;s Canary channel, but they point to a future where React handles more of the UX polish that we currently rely on third-party libraries for.</p><p>What&rsquo;s worth noting is the general pattern across the survey data. The features gaining the most positive attention tend to be the ones that solve focused problems without requiring a wholesale rethink of how we build applications. Developers are drawn to APIs that slot into their existing workflows and make specific things easier, whether that&rsquo;s handling loading states with Suspense, coordinating transitions with <code>&lt;ViewTransition&gt;</code> or managing background rendering with <code>&lt;Activity&gt;</code>.</p><h2 id="the-ui-component-library-landscape">The UI Component Library Landscape</h2><p>The <a target="_blank" href="https://2025.stateofreact.com/en-US/libraries/component-libraries/#component_libraries_cardinalities">survey data around UI component libraries</a> also tells an interesting story. The average respondent has used 2.3 UI libraries and a significant proportion don&rsquo;t use any component library at all. As the survey itself notes, this suggests the space isn&rsquo;t quite settled yet, and that there&rsquo;s still room for new entrants to make their mark.</p><p>What this tells us is that developers are still actively evaluating their options. Even the most widely used libraries in the survey sit around <a target="_blank" href="https://2025.stateofreact.com/en-US/libraries/component-libraries/">50-57% usage</a>, and the libraries with the highest satisfaction rates aren&rsquo;t always the ones with the broadest adoption. The needs are clear: production-quality components, built-in accessibility, consistent theming, TypeScript support and, increasingly, integration with AI-powered development workflows.</p><h3 id="kendoreact">KendoReact</h3><p>For teams building enterprise applications, the choice of component library has long-term implications. It affects how quickly we can ship features, how accessible our applications are out of the box and how well our UI scales across complex use cases like data grids, schedulers and form-heavy workflows.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/whats-next-for-react-kendoreact-home.png?sfvrsn=9f2d81b7_2" alt="KendoReact website: Master the Art of React UI" /></p><p>Progress <a target="_blank" href="https://www.telerik.com/kendo-react-ui">KendoReact</a> is one library worth looking at in this context. It provides <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components">120+ production-ready components</a> with built-in accessibility, deep theming support through <a target="_blank" href="https://www.telerik.com/themebuilder">ThemeBuilder</a> and recent investments in AI-powered developer tooling including an <a target="_blank" href="https://www.telerik.com/react-mcp-servers">MCP server and AI coding assistant</a>. For teams evaluating their UI toolkit for the year ahead, it&rsquo;s a library that&rsquo;s actively investing in the same directions the ecosystem is moving.</p><h2 id="ai-as-an-accelerator">AI as an Accelerator</h2><p>It would be impossible to write about React development in 2026 without mentioning AI. However, the important thing to keep in mind is that AI is changing <strong>how</strong> we write React code, not <strong>what</strong> we build with it.</p><p>The AI tooling landscape has shifted significantly over the last couple of years. AI-native editors like <a target="_blank" href="https://cursor.com/">Cursor</a> and <a target="_blank" href="https://code.claude.com/docs/en/overview">Claude Code</a> understand our entire codebase and can generate components that match our existing patterns and conventions. <a target="_blank" href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a> integrations give AI assistants real-time access to component library documentation, so the code they generate actually uses the right props and follows current best practices.</p><p>These capabilities make us faster by reducing the time we spend on boilerplate and letting us iterate more quickly. However, they don&rsquo;t replace the architectural decisions we need to make: when to adopt Server Components, how to structure our state management and which rendering patterns fit our use case. AI accelerates the execution of those decisions, not the decisions themselves.</p><blockquote><p>For a deeper dive into how AI is reshaping day-to-day React workflows, from code generation to theming to agentic development, check out <a target="_blank" href="https://www.telerik.com/blogs/ai-productivity-react-developers-2026">AI Productivity for React Developers in 2026</a>.</p></blockquote><h2 id="what-this-means-for-2026">What This Means for 2026</h2><p>The data we surveyed today gives us a clear picture of where React is today and where it could be heading in 2026. React is stable, widely adopted and evolving, but the community&rsquo;s appetite for significant change is measured.</p><ul><li><strong>Server Components represent React&rsquo;s most ambitious shift</strong>, but mainstream acceptance will take time. For most teams, the winning approach is incremental adoption, where it solves real problems.</li><li><strong>Developer experience still wins.</strong> The features seeing the strongest adoption and interest (Suspense, ViewTransition, Activity) solve focused problems without demanding that we rebuild our mental model of React.</li><li><strong>The component library landscape remains unsettled.</strong> Teams need libraries that invest in accessibility, developer experience and integrate well with modern tooling, including AI assistants.</li><li><strong>AI is making us more productive at the execution layer</strong>, but strategic decisions about architecture and user experience still require human judgment.</li></ul><p>Looking ahead, the most successful React teams in 2026 will stay pragmatic: adopting new patterns when they solve real problems, choosing stable tooling and using AI to accelerate delivery without losing sight of fundamentals. React&rsquo;s ecosystem is mature enough that we can be selective about what we adopt and when.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">AI Productivity for React Developers in 2026</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Developers are not blindly handing work over to AI, but treating it as a strategic assistant. <a target="_blank" href="https://www.telerik.com/blogs/ai-productivity-react-developers-2026">Here&rsquo;s what this looks like for React devs in 2026.</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17322394.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:f462deac-a01d-48d3-9fca-87eea0bb03f4</id>
    <title type="text">The 10 Best Angular UI Libraries</title>
    <summary type="text">TL;DR What’s the best Angular UI library? If you’re looking for build speed thanks to robust components, AI capabilities, built-in design options and a ready support community, the Progress Kendo UI for Angular library is hard to beat.</summary>
    <published>2026-04-16T14:51:07Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17320248/10-best-angular-ui-libraries"/>
    <content type="text"><![CDATA[<p><span class="featured"><strong>TL;DR What&rsquo;s the best Angular UI library?</strong> If you&rsquo;re looking for build speed thanks to robust components, AI capabilities, built-in design options and a ready support community, the Progress Kendo UI for Angular library is hard to beat.</span></p><p>I&rsquo;ve been developing software for more than 15 years, and the industry never stops changing. Choosing a framework is a strategic decision that must fit the time we are building in.</p><p>Building apps in 2026 is very different from 2015, 2020 or even 2025. Today, picking a UI library is more complex than before. We are no longer just building simple &ldquo;forms and tables&rdquo;; we are building AI-powered experiences. As developers, we now work with AI agents like Cursor and Copilot, and our UI library must be ready for AI from day one.</p><p>The best Angular library does not just save time with CSS or provide basic typical components; it must provide components and tools to help AI and agents understand our code better, saving tokens, time and money for your company, and also help the entire team to work better.</p><p>But as always, the best way to explain the importance of this choice is to look at a real-world situation.</p><h2 id="the-scenario">The Scenario</h2><p>Imagine you were just hired by a startup. You must deliver a complex platform quickly to compete in the market, but your app is modern in the AI era, so it needs more than basic grids and buttons. It will also have complex calendars, real-time charts, chat interfaces and anything needed to perform in accordance with modern user expectations &hellip; and it needs to help our new teammates (AI coding agents) work better and faster.</p><p>In this situation, your UI library is not just about delivering on time. It is about providing a tool that can help you ship your project, support your team with very stable code and provide complex features so you don&rsquo;t have to build everything from zero. You need a library that is already &ldquo;AI-ready,&rdquo; with components made for AI agents and chat interfaces.</p><blockquote><p>I can tell you, from experience, picking the wrong library can mean months of extra work and watching your competitors launch while you are still fixing basic things.</p></blockquote><p>Today, we are going to dive into each framework, showing its strengths and weaknesses, and finally my recommendation for each scenario.</p><h2 id="kendo-ui-for-angular">1. Kendo UI for Angular</h2><p>In the scenario we described, the Progress <a target="_blank" href="https://www.telerik.com/kendo-angular-ui">Kendo UI for Angular</a> component library is your best all-in-one framework. When the deadline is close and the project is high-priority, you do not have time to build everything from scratch.</p><h3 id="why-kendo-ui-is-the-best-tool-for-projects">Why Kendo UI Is the Best Tool for Projects</h3><ul><li><p><strong>Built for speed:</strong> With <strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui#components">120+ components</a></strong> (Grid, Excel, Charts, Scheduler, etc.) and ready-to-use <a target="_blank" href="https://www.telerik.com/page-templates-and-ui-blocks"><strong>Page Templates</strong></a>, you will be assembling your app quickly. You can save <em>weeks</em> of work and focus on the important parts of your code while an entire team at Progress manages the upkeep of your component library.</p></li><li><p><strong><a target="_blank" href="https://www.telerik.com/mcp-servers">MCP Server</a>:</strong> Kendo UI is an AI-ready framework for developers and agents to increase productivity. Today, every hour (and token) is valuable. The Kendo UI MCP Server helps your AI agents (like VS Code or Cursor) write better code by following Kendo UI best practices that come directly from the team that built the software, reducing hallucinations and allowing your developers and agents ship code fast yet with the highest quality.</p></li><li><p><strong><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-components">AI-ready Angular components</a>:</strong> Add intelligent features quickly with components like AIPrompt, SmartPaste or AI features in the Grid. This makes your app look modern with very little effort.</p></li><li><p><strong><a target="_blank" href="https://themebuilder.telerik.com/">ThemeBuilder</a>:</strong> Avoid wasting time on CSS. You can import design tokens from Figma and get your style right the first time without writing thousands of lines of code.</p></li><li><p><strong><a target="_blank" href="https://www.telerik.com/purchase/support-plans">Expert help:</a></strong> The tier-based support plans give you peace of mind. If you have a problem at a critical moment, you have a team of professional engineers to help you, not just a public forum (but the community support is also robust!).</p></li></ul><p><strong>Pro tip:</strong> You can <a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui">try Kendo UI for Angular completely free</a> for 30 days, <strong>no credit card required</strong>. It&rsquo;s the fastest way to prove to your team that you can meet that &ldquo;impossible&rdquo; deadline.</p><p>But what if you are looking for Google&rsquo;s official baseline? That brings us to our next pick.</p><h2 id="angular-material">2. Angular Material</h2><p><a target="_blank" href="https://material.angular.io/">Angular Material</a> is the official UI suite maintained by the Angular team. It implements Google&rsquo;s Material Design and is often the first choice for developers looking for a standard, predictable foundation. While it&rsquo;s the community baseline, in 2026, many projects find its rigid structure a limiting factor.</p><p>If you&rsquo;ve ever worked in an Angular project, you&rsquo;ve probably used it. It&rsquo;s the industry standard for a reason, but standards aren&rsquo;t always enough for every project.</p><ul><li><p><strong>The Good:</strong> It is very stable. Because it is built by the same team that makes Angular, it always works with the latest versions. It follows Google&rsquo;s Material Design and looks clean and professional.</p></li><li><p><strong>The Cons:</strong> The customization is difficult. If you need a unique look for your brand, you will spend a lot of time fighting with CSS. It also lacks advanced components for big data, like high-performance Grids or Calendars.</p></li><li><p><strong>My Feedback:</strong> I have seen many teams spend weeks trying to make Material look different. Also, Material lacks complex tools like a <strong>Scheduler</strong> or a <strong>Gantt Chart</strong>. If your project needs more than just basic forms, I recommend <strong>Kendo UI</strong>. It gives you 120+ advanced parts that are ready for big business needs, saving you the work of building them yourself.</p></li></ul><p>If Material feels too rigid and you need a massive variety of &ldquo;out-of-the-box&rdquo; gadgets, you might want to consider our next option.</p><h2 id="primeng">3. PrimeNG</h2><p><a target="_blank" href="https://primeng.org/">PrimeNG</a> is one of the well-known libraries in the ecosystem with a collection of components. While its catalog is extensive, navigating its many options can require a significant investment in terms of styling and consistency in larger apps.</p><ul><li><p><strong>The Good:</strong> Their variety is unmatched. PrimeNG has many components from simple buttons to complex organization charts.</p></li><li><p><strong>The Cons:</strong> Maintaining a perfectly consistent visual style across many complex components can become tricky in large-scale projects. The styling system can sometimes feel overwhelming when you need deep, unified brand customization.</p></li><li><p><strong>My Feedback:</strong> Kendo UI provides more than just a framework. It offers tools like ThemeBuilder, Figma kits, MCP tools and AI-ready components to provide a smooth developer experience.</p></li></ul><p>Now, if your project is heavily focused on data and complex dashboards, check out the next player in the game.</p><h2 id="syncfusion-angular">4. Syncfusion Angular</h2><p><a target="_blank" href="https://www.syncfusion.com/angular-ui-components">Syncfusion</a> is an enterprise-focused suite that offers a wide range of specialized components.</p><ul><li><p><strong>The Good:</strong> Their charts and grids are powerful. They are a good choice for data-heavy applications and dashboards.</p></li><li><p><strong>The Cons:</strong> The API does not always feel like standard Angular, which makes it harder to learn. It is also difficult to customize the components to match a Figma design. This is a framework built for developers, but real-world projects need to include the whole team.</p></li><li><p><strong>My Feedback:</strong> We should not waste time manually syncing Figma styles with components. I prefer Kendo UI because it is designed for developers, designers and AI agents. This provides a smooth experience for everyone on the team.</p></li></ul><p>Speaking of data apps, sometimes you don&rsquo;t need a full suite. If your app is 90% tables, then you should take a look at the next one.</p><h2 id="ag-grid">5. AG Grid</h2><p><a target="_blank" href="https://www.ag-grid.com/angular-data-grid/">AG Grid</a> is a specialized library almost entirely focused on data grids. It&rsquo;s a common choice for applications where tabular data is the main focus, though it requires pairing with other libraries for common UI elements like buttons and modals.</p><ul><li><p><strong>The Good:</strong> If your application is 90% tables, this is a great choice. Its performance with very large datasets is good.</p></li><li><p><strong>The Cons:</strong> The real world is not only a grid. You will still need to find and style other libraries to solve other scenarios in your app, often resulting in a UI with inconsistent styles that is hard to maintain.</p></li><li><p><strong>My Feedback:</strong> AG Grid is powerful, but it is only one part of what you&rsquo;ll need. Kendo UI gives you that same grid power in a <strong>unified system</strong>. This means your Grid, Buttons and Charts all share the same style and logic. Plus, Kendo UI has built-in <strong>PDF and Excel exporting</strong> that works perfectly without extra work.</p></li></ul><p>But what if you need that speed in more than just tables? Let&rsquo;s see the next option.</p><h2 id="ignite-ui-for-angular">6. Ignite UI for Angular</h2><p><a target="_blank" href="https://www.infragistics.com/products/ignite-ui-angular">Ignite UI for Angular</a> is focused on data visualization and performance. It&rsquo;s often used in scenarios where real-time charts are a priority, though its ecosystem is smaller than other major players.</p><ul><li><p><strong>The Good:</strong> Great charts and a very fast rendering engine, especially for real-time data.</p></li><li><p><strong>The Cons:</strong> The ecosystem and community are smaller than the large libraries on this list. This can make it harder to find help for specific problems. Also, similar to AG Grid, in the real world our apps are more than a dashboard. They work with a combination of features that take time to build by yourself.</p></li><li><p><strong>My Feedback:</strong> Unlike Ignite UI, Kendo UI has Building Blocks that can solve common scenarios like login, register, forgot password, etc. Kendo UI has a very large community and <strong>professional support</strong>. This means you can always get help when you are stuck, making it a safer choice for important projects.</p></li></ul><p>If you&rsquo;re already familiar with the DevExpress environment from other platforms, then you&rsquo;ll feel right at home with our next pick.</p><h2 id="devextreme-angular">7. DevExtreme Angular</h2><p><a target="_blank" href="https://js.devexpress.com/Angular/">DevExtreme Angular</a> by DevExpress has its roots in traditional software development, carrying over patterns from WinForms and ASP.NET. This makes it a familiar choice for teams coming from a more classical enterprise background, though its developer experience can feel a bit dated for modern web standards.</p><ul><li><p><strong>The Good:</strong> Perfect for developers who already use DevExpress and need complex reporting tools and traditional business components.</p></li><li><p><strong>The Cons:</strong> It can feel a bit &ldquo;old&rdquo; in terms of how you write code. Changing the CSS to meet modern web standards is often a difficult and long task.</p></li><li><p><strong>My Feedback:</strong> To avoid the old code patterns of DevExtreme, Kendo UI offers a clean, <strong>modern architecture</strong>. It helps you build fast without creating messy code that is hard to fix later.</p></li></ul><p>For those who prefer a clean, minimal design system like Ant Design, there&rsquo;s a popular community implementation you should know about.</p><h2 id="ng-zorro">8. NG-ZORRO</h2><p><a target="_blank" href="https://ng.ant.design/">NG-ZORRO</a> is a community-driven implementation of the Ant Design system for Angular. It&rsquo;s aimed at developers who want a ready-made aesthetic for dashboards. However, its community-based nature means it lacks the dedicated professional support found in commercial suites.</p><ul><li><p><strong>The Good:</strong> It creates beautiful and professional-looking interfaces immediately. Excellent for internal tools where speed is important.</p></li><li><p><strong>The Cons:</strong> It is very strict about its design. If you need to change the style or keep the same look across React or Vue teams, you will find it limited.</p></li><li><p><strong>My Feedback:</strong> Community libraries like NG-ZORRO are good until you find a difficult bug. In a startup, time is money. Kendo UI enterprise support gets you help from the engineers who actually built the tools. This <strong>professional protection</strong> is why big companies prefer Kendo UI.</p></li></ul><p>If your focus is less on &ldquo;fancy&rdquo; and more on extreme accessibility and enterprise clean-room aesthetics, then check this out.</p><h2 id="clarity-design-system">9. Clarity Design System</h2><p><a target="_blank" href="https://clarity.design/">Clarity</a> is a design system developed by VMware. It was built for their own internal products and emphasizes a specific enterprise-style UX. While it&rsquo;s highly focused on its specific patterns, customizing it to fit a broader range of brand identities can be a challenge.</p><ul><li><p><strong>The Good:</strong> Strong focus on user experience and accessibility. It is built by VMware, so it is proven to work well for complex business tasks.</p></li><li><p><strong>The Cons:</strong> The look is very specific to VMware. It is difficult to change the style for a brand that wants to look unique.</p></li><li><p><strong>My Feedback:</strong> Clarity is very accessible, but it is hard to change. If your designers use Figma, Kendo UI is a much better choice. Progress Kendo UI provides <strong>Figma UI Kits</strong> that match the components exactly. This means your developers can build exactly what the designers created without mistakes.</p></li></ul><p>Finally, if you are the type of developer who loves full modularity and building your own patterns like LEGO, you&rsquo;ll love our final pick.</p><h2 id="taiga-ui">10. Taiga UI</h2><p><a target="_blank" href="https://taiga-ui.dev/">Taiga UI</a> is a library that focuses on modularity and a TypeScript-first approach. It&rsquo;s designed for developers who enjoy building their UI piece by piece, though this modularity often means a slower development cycle compared to more pre-integrated solutions.</p><ul><li><p><strong>The Good:</strong> It uses modular principles, making it perfect for developers who want to build their own patterns from zero.</p></li><li><p><strong>The Cons:</strong> Building a complex business dashboard this way is a slow process. In a startup, you don&rsquo;t have months to build every part; you have weeks to launch.</p></li><li><p><strong>My Feedback:</strong> Taiga UI is fun for small experiments, but for real business, you need speed. Kendo UI gives you the same modular power but with <strong>120+ ready-to-use components</strong>. You don&rsquo;t have to build the &ldquo;LEGO blocks&rdquo; yourself. Kendo UI gives you the whole castle ready to use.</p></li></ul><h2 id="recap">Recap</h2><p>Today, building software is a team effort. It is for designers, developers and AI agents. Choosing a framework that thinks about all these factors is the key to a successful product.</p><p>In 2026, picking a framework is a strategic decision that defines the success of your project. Choosing <strong>Kendo UI for Angular</strong> means giving your team the best tools for modern development. With features like the <a target="_blank" href="https://www.telerik.com/mcp-servers"><strong>MCP Server</strong></a>, <a target="_blank" href="https://themebuilder.telerik.com/"><strong>ThemeBuilder</strong></a> and <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-components"><strong>AI-ready components</strong></a>, Kendo UI for Angular helps you build applications faster and handle future challenges.</p><p><strong>Do you want to try it?</strong> </p><p><a target="_blank" href="https://www.telerik.com/try/kendo-angular-ui" class="Btn">Download Free Trial</a></p><img src="https://feeds.telerik.com/link/10827/17320248.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:156049da-af6d-45bc-9ca5-40a634a963f3</id>
    <title type="text">AI Productivity for React Developers in 2026</title>
    <summary type="text">Developers are not blindly handing work over to AI, but treating it as a strategic assistant. Here’s what this looks like for React devs in 2026.</summary>
    <published>2026-04-13T16:03:30Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17318467/ai-productivity-react-developers-2026"/>
    <content type="text"><![CDATA[<p><span class="featured">Developers are not blindly handing work over to AI, but treating it as a strategic assistant. Here&rsquo;s what this looks like for React devs in 2026.</span></p><p>The 2025 Stack Overflow Developer Survey confirmed what most of us already know from experience: <a target="_blank" href="https://survey.stackoverflow.co/2025/ai/#1-ai-tools-in-the-development-process">84% of developers are now using or planning to use AI tools in their development process.</a> What&rsquo;s more revealing is how developers are still being cautious in their use of these tools. Only about <a target="_blank" href="https://survey.stackoverflow.co/2025/ai/#2-accuracy-of-ai-tools">one-third say they trust AI outputs completely, while nearly half question their accuracy</a>.</p><p>This cautious adoption tells us something important. We&rsquo;re not blindly handing our work over to AI. We&rsquo;re learning to use these tools strategically, treating them as assistants rather than replacements. For React developers specifically, this means adapting how we approach building interfaces, managing component libraries and iterating on features.</p><p>If you&rsquo;ve been building React applications for a while, you&rsquo;ve probably experienced this shift firsthand. Maybe you&rsquo;re using <a target="_blank" href="https://github.com/features/copilot">GitHub Copilot</a> to autocomplete component logic, or <a target="_blank" href="https://cursor.com/">Cursor</a> to refactor entire feature branches. Perhaps you&rsquo;re generating design themes with <a target="_blank" href="https://www.telerik.com/themebuilder">Progress ThemeBuilder</a>, or using <a target="_blank" href="https://claude.ai/">Claude</a> to debug complex state management issues. These aren&rsquo;t experimental workflows anymore; they&rsquo;re part of how we work.</p><p>In this article, we&rsquo;ll explore what this actually looks like in practice for React developers in 2026.</p><h2 id="coding-itself-has-changed">Coding Itself Has Changed</h2><p>There&rsquo;s a shift in how we write code, and it&rsquo;s not what most people expected (don&rsquo;t worry, we&rsquo;re not being replaced by AI). We&rsquo;re spending less time on the mechanical act of typing and more time on the creative work of guiding and refining.</p><p><a target="_blank" href="https://tinyclouds.org/">Ryan Dahl</a>, the creator of <a target="_blank" href="https://nodejs.org/en">Node.js</a> and <a target="_blank" href="https://deno.com/">Deno</a>, aptly put it in a recent social media post: <em>&ldquo;This has been said a thousand times before, but allow me to add my own voice: the era of humans writing code is over. Disturbing for those of us who identify as SWEs, but no less true. That&rsquo;s not to say SWEs don&rsquo;t have work to do, but writing syntax directly is not it.&rdquo;</em></p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/ai-productivity-for-react-ryan-dahl-x-post.png?sfvrsn=6d572b2c_2" alt="" /></p><p>For React developers, this means our day-to-day work looks different. Instead of spending an hour carefully typing out a form component with all its validation logic, we might spend 10 minutes describing what we need and then another 20 minutes reviewing and adjusting the generated code. The result? The same quality component in a fraction of the time.</p><p>The iteration cycles have also become faster. When we can generate and refine code quickly, we&rsquo;re more willing to try multiple approaches. Need to see how our data grid looks with server-side pagination versus client-side? Generate both implementations and compare them. Want to experiment with different state management patterns? We can prototype each one in minutes rather than hours.</p><p>We still need to understand React&rsquo;s rendering model, still need to know when to use <code>useCallback</code> versus <code>useMemo</code>, still need to architect our component hierarchy thoughtfully. However, we don&rsquo;t need to manually type out every property definition or write boilerplate code repeatedly.</p><h2 id="continuous-context-and-ai-native-editors">Continuous Context and AI-Native Editors</h2><p>One of the significant improvements in AI-assisted development is <strong>persistent context</strong>. Modern AI coding assistants understand our entire codebase, not just the file we&rsquo;re currently editing.</p><p>Traditional autocomplete tools could only see a few lines around our cursor. They might suggest completing a function name, but they had no idea what that function should actually do or how it fit into our broader application architecture. AI-native editors and tools like <a target="_blank" href="https://cursor.com/">Cursor</a>, <a target="_blank" href="https://code.claude.com/docs/en/overview">Claude Code</a> and others have changed this completely.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/ai-productivity-for-react-cursor.jpg?sfvrsn=e557aff2_2" alt="" /></p><p>These tools help maintain an understanding of our project structure, our component patterns, our naming conventions and even our coding style. When we ask them to create a new feature, they don&rsquo;t just generate generic React code; they generate code that looks like it belongs in our codebase because they&rsquo;ve learned from the rest of our application.</p><p>Let&rsquo;s say we&rsquo;re building a <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/sample-applications/admin-dashboard">React dashboard</a> UI that needs to fetch user data and display it in a <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid">table</a>. With traditional tools, we&rsquo;d manually wire up the API call, handle loading states, manage errors and set up the table structure.</p><p>With an AI assistant that understands our codebase, we can describe what we need, and it&rsquo;ll generate code that uses our existing API client, follows our established error handling patterns and implements the table using whatever libraries we&rsquo;re already using in our app.</p><p>Working with these AI assistants feels less like using a tool and more like pair programming with someone who already knows our codebase inside and out.</p><h2 id="planning-and-agentic-workflows">Planning and Agentic Workflows</h2><p>Writing individual functions or components is one thing, but modern AI tools are increasingly capable of handling multi-step workflows that require planning and coordination. This is where agentic AI comes in: systems that can break down complex tasks into smaller steps and execute them sequentially or in parallel.</p><p>For React developers, this means we can hand off entire features to an AI agent rather than just asking for help with specific files. Want to migrate from <a target="_blank" href="https://redux.js.org/">Redux</a> to <a target="_blank" href="https://github.com/pmndrs/zustand">Zustand</a>? An AI agent can analyze our current state management setup, create a migration plan, and then systematically work through each component to update the implementation.</p><p>For example, we might tell an agent: <em>&ldquo;Build a column chart that shows quarterly sales by region, with drill-down functionality to reveal monthly data when a quarter is clicked.&rdquo;</em></p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/kendoreact-chart-mcp.gif?sfvrsn=86c7ffc8_1" alt="" /></p><p>The agent can generate the chart component, configure the data bindings, create any necessary helper functions for aggregation and even add default styling or tooltips.</p><p>What&rsquo;s powerful is that these agents can handle refactors and migrations that would be tedious to do manually. Need to rename a prop across 50 components? Want to update all your class components to functional components with hooks? Planning to migrate from JavaScript to TypeScript? These are exactly the kinds of repetitive-but-important tasks that agents excel at.</p><h2 id="ui-generation-design-and-theming-with-ai">UI Generation, Design and Theming with AI</h2><p>Building user interfaces has always involved a fair amount of repetitive work. Creating layouts, styling components and maintaining design consistency across an application. These are all necessary tasks, but they&rsquo;re not where some developers want to spend their creative energy.</p><p>With AI, layout generation has become surprisingly sophisticated. Tools like <a target="_blank" href="https://v0.dev/">v0</a> can take a description or even a screenshot of a design and generate React components that implement that layout. The code they produce isn&rsquo;t just a rough approximation either, it&rsquo;s good-quality JSX with proper semantic HTML, accessibility attributes and responsive styling.</p><p>For React developers working with component libraries like <a target="_blank" href="https://www.telerik.com/kendo-react-ui">Progress KendoReact</a>, this gets even more powerful as long as the agent has the appropriate context about the library and components. Instead of generating generic HTML and CSS, these tools can generate code using the actual components from our library. Need a data grid with sorting, filtering and pagination? Describe it, and the AI will scaffold it using the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid">React Grid</a> component we&rsquo;re already using elsewhere in our app: with all the right props, event handlers and styling.</p><p><a target="_blank" href="https://www.telerik.com/themebuilder">Progress ThemeBuilder</a> has taken this a step further with AI-powered theme generation. Instead of manually configuring dozens of color variables and spacing values, we can describe the aesthetic we&rsquo;re going for in plain English: <em>&ldquo;Create a clean, modern analytics theme with a cool blue-gray palette that feels data-driven and professional, suitable for a B2B software dashboard.&rdquo;</em> The AI generates a complete design system that applies consistently across all our components.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/theme-builder-modern-analytics-theme.png?sfvrsn=8a4ac335_2" alt="" /></p><p>What makes this valuable is that the generated themes aren&rsquo;t just random color combinations. The AI understands design principles like color theory, contrast ratios for accessibility and how different UI states should relate visually. When it generates a theme, it&rsquo;s creating a system where primary buttons, hover states, focus indicators and disabled states all work together. ThemeBuilder also provides component-level AI theming, which means we can refine specific components without having to regenerate our entire theme.</p><p>For developers who aren&rsquo;t designers (which is most of us), this removes a huge source of friction. We can focus on building functionality while still delivering applications that look professional and cohesive.</p><h2 id="model-context-protocol-and-integration">Model Context Protocol and Integration</h2><p>The tools we&rsquo;ve talked about so far are impressive, but they have a limitation: they only know what&rsquo;s in our codebase and what they learned during training. They don&rsquo;t know about our specific component library&rsquo;s latest features, our company&rsquo;s internal design system, or the proprietary APIs we&rsquo;re building against.</p><p>This is where <a target="_blank" href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a> becomes important. Think of MCP as a standardized way for AI models to connect to external knowledge sources and tools. Instead of the AI being limited to its training data, it can reach out to documentation, component libraries, databases and other resources to get current, specific information about the tools we&rsquo;re using.</p><p>For React developers, this means our AI assistant can understand the actual API of the components we&rsquo;re working with, not just generic React patterns. When we&rsquo;re using the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/grid">KendoReact Grid</a> component, an MCP-enabled assistant can query the real documentation to understand what props are available, what patterns are recommended and what changed in the latest version.</p><p>The <a target="_blank" href="https://www.npmjs.com/package/@progress/kendo-react-mcp">@progress/kendo-react-mcp</a> server implements this for KendoReact. When we install it and configure it with our editor, the AI assistant gains access to comprehensive knowledge about KendoReact components. It&rsquo;s not guessing at how to use the Grid, or the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/scheduler">Scheduler</a>, or the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/dateinputs/datepicker">DatePicker</a>. It&rsquo;s referencing the actual documentation and best practices.</p><p>Here&rsquo;s an example of generating a KendoReact Grid from scratch with the help of the <code>@progress/kendo-react-mcp</code> tool.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/kendoreact-grid-mcp.gif?sfvrsn=98fc03b_2" alt="" /></p><p>This can make a large difference in practice. Without MCP, we might ask an AI to create a KendoReact Grid component and get something that looks right but uses outdated props or misses important configuration options. With MCP, the AI generates code that follows current best practices and uses the component exactly as it was designed to be used.</p><h2 id="quality-review-and-governance">Quality, Review and Governance</h2><p>As AI-generated code becomes more prevalent in React applications, questions about quality and review processes naturally arise. The good news is that AI isn&rsquo;t just generating code; it&rsquo;s starting to get better at reviewing and improving it.</p><p>Modern AI tools can perform code reviews that go beyond syntax checking. They understand architectural patterns, can spot potential performance issues and flag accessibility concerns. When we commit changes, these tools can analyze the diff and provide feedback similar to what a developer might offer in a pull request review.</p><p>For React applications specifically, this means AI can catch issues like unnecessary re-renders, missing dependency arrays in hooks, improper state management patterns or components that should be memoized but aren&rsquo;t. It can also verify that our components follow established patterns in our codebase and flag when new code deviates from those conventions.</p><p>AI-generated code can greatly accelerate development and improve consistency, but <strong>it&rsquo;s not a replacement for human judgment</strong>. Even the more sophisticated tools benefit from a steward: a developer who reviews, guides and validates the AI&rsquo;s output. Humans remain essential for making nuanced decisions, verifying alignment with project goals and upholding quality standards that AI alone can&rsquo;t fully guarantee. In other words, AI can handle the heavy (repetitive) lifting, but the final responsibility still rests with us.</p><h2 id="what-this-means-for-day-to-day-react-work">What This Means for Day-to-Day React Work</h2><p>What does all of this actually look like in practice? How has the daily experience of building React applications changed? Here are some takeaways:</p><ul><li><strong>Faster iteration</strong>: Features that used to take days can now be scaffolded in hours. AI handles repetitive boilerplate, letting us focus on logic, architecture and user experience.</li><li><strong>Less boilerplate, more creativity</strong>: Forms, API integrations, responsive layouts and other common patterns can be generated automatically, freeing us to solve higher-level problems.</li><li><strong>Higher-level decisions matter more</strong>: Understanding React fundamentals like component composition, state management, performance optimization and accessibility is more important than ever because we&rsquo;re guiding and reviewing AI outputs rather than typing everything ourselves.</li></ul><p>In short, React development in 2026 is faster, more iterative and more creative. The mechanical work of writing boilerplate and translating designs into components is largely automated, but the critical thinking and stewardship still require skilled developers.</p><h2 id="ready-to-explore-how-ai-can-enhance-your-react-development">Ready to Explore How AI Can Enhance Your React Development?</h2><ul><li>Give the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-components#kendoreact-ai-components-and-features">KendoReact AI Components and Features</a> a try.</li><li>Watch demos of <a target="_blank" href="https://www.telerik.com/themebuilder">AI-powered theme generation in ThemeBuilder</a> to see how design ideation can be automated.</li><li>Try the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/ai-assistant/getting-started">KendoReact MCP Server</a> to give your AI assistant deep knowledge of KendoReact components.</li><li>Check out the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/ai-assistant/prompt-library">KendoReact Prompt Library</a> for ready-made prompts that target common development scenarios.</li></ul><img src="https://feeds.telerik.com/link/10827/17318467.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bd4d6447-7473-40eb-b916-7931c305c4b7</id>
    <title type="text">Simplified Authentication with Better Auth</title>
    <summary type="text">We’ll build a complete email and password authentication system with session management to see how Better Auth works. Users will be able to sign up, log in and stay authenticated across page refreshes. We’ll use Next.js, Drizzle ORM and SQLite.</summary>
    <published>2026-04-09T17:26:49Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17316560/simplified-authentication-better-auth"/>
    <content type="text"><![CDATA[<p><span class="featured">We&rsquo;ll build a complete email and password authentication system with session management to see how Better Auth works. Users will be able to sign up, log in and stay authenticated across page refreshes. We&rsquo;ll use Next.js, Drizzle ORM and SQLite.</span></p><p>Authentication should be straightforward, but in practice, it takes time. You&rsquo;re dealing with sessions, cookies, password hashing, OAuth redirects and email verification, and each piece has its own quirks and scattered documentation. The whole time, you may also be asking yourself if you&rsquo;re doing this securely. One mistake exposes your users.</p><p>Most existing tools don&rsquo;t help much. <a target="_blank" href="https://next-auth.js.org/">NextAuth</a> is solid if you&rsquo;re using Next.js, but switching frameworks means starting over. Building from scratch gives you control at the cost of maintaining every security detail yourself. <a target="_blank" href="https://clerk.com/">Clerk</a> and <a target="_blank" href="https://auth0.com/">Auth0</a> work well until you reach their pricing tiers or require a feature they don&rsquo;t support. <a target="_blank" href="https://www.passportjs.org/">Passport.js</a> works, but it hasn&rsquo;t aged well&mdash;there&rsquo;s callback hell and endless boilerplate.</p><p><a target="_blank" href="https://www.better-auth.com/">Better Auth</a> takes a different approach. It&rsquo;s lightweight, works with any framework (<a target="_blank" href="https://nextjs.org/">Next.js</a>, <a target="_blank" href="https://remix.run/">Remix</a>, <a target="_blank" href="https://svelte.dev/">Svelte</a>, etc.), and comes with <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a> baked in. You get OAuth, 2FA and password resets out of the box. You can also customize when you need to, or just use the defaults.</p><p>In this guide, we&rsquo;ll build a complete email and password authentication system with session management to see how Better Auth works. Users will be able to sign up, log in and stay authenticated across page refreshes. We&rsquo;ll use Next.js, Drizzle ORM and SQLite.</p><h2 id="prerequisites">Prerequisites</h2><p>To follow along with this guide, you&rsquo;ll need:</p><ul><li>A decent understanding of JavaScript/TypeScript</li><li>Basic familiarity with React and Next.js</li><li>A database ready (<a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a>, <a target="_blank" href="https://www.mysql.com/">MySQL</a> or <a target="_blank" href="https://sqlite.org/">SQLite</a>)</li></ul><h2 id="what-is-better-auth">What Is Better Auth?</h2><p>Better Auth is a TypeScript-first authentication library that avoids the usual trade-offs like vendor lock-in, expensive monthly fees or complex configurations. It is framework-agnostic, so you can use it with Next.js today and switch to Remix or SvelteKit tomorrow without rewriting your auth logic. It&rsquo;s not a managed service, so there&rsquo;s no per-user pricing or vendor lock-in.</p><p>What makes it different? It&rsquo;s database-agnostic, meaning you can use PostgreSQL, MySQL, SQLite or MongoDB. Better Auth adapts through adapters like Prisma, Drizzle and Mongoose. It&rsquo;s type-safe by default, built in TypeScript from the ground up, so your IDE knows what methods exist, what data comes back, and catches errors before you run your code. It also has security measures built in by default, with proper password hashing, HttpOnly cookies, CSRF protection and secure session management. The defaults follow best practices.</p><p>Better Auth supports OAuth providers (Google, GitHub, etc.), magic links, two-factor authentication, passkeys, even enterprise SSO, and more than what we&rsquo;ll cover in this article. We&rsquo;re focusing on the fundamentals so you understand how the system works. Once you grasp the basics, adding these features is straightforward through Better Auth&rsquo;s plugin system.</p><h2 id="project-setup">Project Setup</h2><p>Let&rsquo;s start by creating a new Next.js project with TypeScript and <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a>. Run the following command in your terminal:</p><pre class=" language-shell"><code class="prism  language-shell">npx create-next-app@latest better-auth --ts --tailwind --eslint --app
</code></pre><p>This creates a Next.js project with TypeScript, Tailwind CSS, ESLint and App Router (the modern Next.js routing system) configured.</p><p>Run this command to navigate into the project:</p><pre class=" language-shell"><code class="prism  language-shell">cd better-auth
</code></pre><p>Run the following command to install Better Auth and its dependencies:</p><pre class=" language-shell"><code class="prism  language-shell">npm install better-auth drizzle-orm better-sqlite3 
npm install -D drizzle-kit @types/better-sqlite3
</code></pre><h2 id="database-setup">Database Setup</h2><p>Before configuring Better Auth, we need to prepare our database. We&rsquo;ll break this down into three distinct files to keep things organized:</p><ul><li><strong>Define the tables</strong>: Create the tables Better Auth needs (users, sessions, accounts, verification)</li><li><strong>Database initialization</strong>: Set up the SQLite connection and wrap it with Drizzle for type-safe queries</li><li><strong>Configure auth</strong>: Finally, we connect our database to Better Auth using the Drizzle adapter</li></ul><h3 id="define-the-tables">Define the Tables</h3><p>We need to explicitly define the tables Better Auth expects. We will add all of these to a single file. Open your <code>lib/auth-schema.ts</code> file and add the following to it:</p><p><strong>The User Table</strong><br />This stores basic user information.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//lib/auth-schema.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> sqliteTable<span class="token punctuation">,</span> text<span class="token punctuation">,</span> integer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"drizzle-orm/sqlite-core"</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token function">sqliteTable</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">primaryKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  name<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  email<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"email"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">unique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  emailVerified<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"emailVerified"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"boolean"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  image<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"image"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  createdAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"createdAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  updatedAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"updatedAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The <code>email</code> field is unique, meaning no two users can share the same email. Better Auth requires these specific fields for its internal functionality.</p><p>Notice we mark <code>name</code> and <code>email</code> as <code>notNull()</code> here. This is important because Better Auth infers its types from this schema. By making them required in the database, TypeScript will automatically force us to provide them in the sign-up form later.</p><p><strong>The Session Table</strong><br />This manages active login sessions.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//lib/auth-schema.ts</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> session <span class="token operator">=</span> <span class="token function">sqliteTable</span><span class="token punctuation">(</span><span class="token string">"session"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">primaryKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  expiresAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"expiresAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  token<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"token"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">unique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  createdAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"createdAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  updatedAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"updatedAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  ipAddress<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"ipAddress"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  userAgent<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"userAgent"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  userId<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"userId"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">references</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> user<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Each session has a unique token and links to a user via <code>userId</code>. The <code>expiresAt</code> timestamp determines when the session becomes invalid.</p><p><strong>The Account Table</strong><br />This handles OAuth providers and stores authentication credentials.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//lib/auth-schema.ts</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> account <span class="token operator">=</span> <span class="token function">sqliteTable</span><span class="token punctuation">(</span><span class="token string">"account"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">primaryKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  accountId<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"accountId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  providerId<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"providerId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  userId<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"userId"</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">references</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> user<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span>
  accessToken<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"accessToken"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  refreshToken<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"refreshToken"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  idToken<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"idToken"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  accessTokenExpiresAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"accessTokenExpiresAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  refreshTokenExpiresAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"refreshTokenExpiresAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
    mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  scope<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"scope"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  password<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  createdAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"createdAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  updatedAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"updatedAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The account table stores the user&rsquo;s credentials, including the password. This separation keeps the architecture flexible, allowing you to link other providers like GitHub or Google to the same user identity.</p><p><strong>Verification Table</strong><br />This stores temporary code for email verification and password resets.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> verification <span class="token operator">=</span> <span class="token function">sqliteTable</span><span class="token punctuation">(</span><span class="token string">"verification"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">primaryKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  identifier<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"identifier"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  value<span class="token punctuation">:</span> <span class="token function">text</span><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  expiresAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"expiresAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  createdAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"createdAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  updatedAt<span class="token punctuation">:</span> <span class="token function">integer</span><span class="token punctuation">(</span><span class="token string">"updatedAt"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token punctuation">:</span> <span class="token string">"timestamp"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This table acts as a temporary vault for security tokens. When the system sends an email to verify a user, the unique code is stored here to ensure the link is valid and hasn&rsquo;t expired when clicked.</p><h3 id="database-initialization">Database Initialization</h3><p>We need a running database connection. Let&rsquo;s create a file called <code>lib/db.ts</code>. This is where we initialize SQLite and wrap it in Drizzle so we can use it everywhere else. We wrap it because we want to write TypeScript, not SQL strings. By passing the connection to Drizzle, you get to query your database using typed methods.</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">// lib/db.ts </span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> drizzle <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"drizzle-orm/better-sqlite3"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> Database <span class="token keyword">from</span> <span class="token string">"better-sqlite3"</span><span class="token punctuation">;</span>

<span class="token comment">// This creates a local 'sqlite.db' file if it doesn't exist</span>
<span class="token keyword">const</span> sqlite <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Database</span><span class="token punctuation">(</span><span class="token string">"sqlite.db"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 

<span class="token keyword">export</span> <span class="token keyword">const</span> db <span class="token operator">=</span> <span class="token function">drizzle</span><span class="token punctuation">(</span>sqlite<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>We need to do this first so we can import <code>db</code> into our auth configuration without TypeScript yelling at us.</p><h3 id="configure-auth">Configure Auth</h3><p>Now we can write the auth config. This file is where we tell Better Auth about our database and configure how users will authenticate.</p><p>Create a <code>lib/auth.ts</code> file and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//lib/auth.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> betterAuth <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"better-auth"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> drizzleAdapter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"better-auth/adapters/drizzle"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> db <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./db"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> schema <span class="token keyword">from</span> <span class="token string">"./auth-schema"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> auth <span class="token operator">=</span> <span class="token function">betterAuth</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  database<span class="token punctuation">:</span> <span class="token function">drizzleAdapter</span><span class="token punctuation">(</span>db<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    provider<span class="token punctuation">:</span> <span class="token string">"sqlite"</span><span class="token punctuation">,</span>
    schema<span class="token punctuation">:</span> schema<span class="token punctuation">,</span> <span class="token comment">// passing the schema here</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  emailAndPassword<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    enabled<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre><p>In the code above, we import the database connection <code>db</code> and <code>schema</code> we created, then pass them to Better Auth through the drizzleAdapter. This adapter translates Better Auth operations into Drizzle queries so it can read and write user data.</p><p>The <code>provider: "sqlite"</code> tells Better Auth we&rsquo;re using SQLite. If we were using something else like PostgreSQL, we would change it to whatever we&rsquo;re using.</p><p>It is important to note that the <code>emailAndPassword: {enabled: true}</code> option activates email and password authentication. Better Auth will generate the signup and signin endpoints we need.</p><h2 id="syncing-database">Syncing Database</h2><p>Now that we have three core files, we need to create the database file. Currently, <code>sqlite.db</code> doesn&rsquo;t exist.</p><h3 id="drizzle-configuration">Drizzle Configuration</h3><p>Create a file named <code>drizzle.config.ts</code> at the your root of your project and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//drizzle.config.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"drizzle-kit"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  schema<span class="token punctuation">:</span> <span class="token string">"./lib/auth-schema.ts"</span><span class="token punctuation">,</span> <span class="token comment">// where our tables will be defined</span>
  dialect<span class="token punctuation">:</span> <span class="token string">"sqlite"</span><span class="token punctuation">,</span>
  dbCredentials<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    url<span class="token punctuation">:</span> <span class="token string">"sqlite.db"</span><span class="token punctuation">,</span> <span class="token comment">// This is the name of the file it will create</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Here, we tell Drizzle where to find our schema and what database to use. The config points to our schema file <code>./lib/auth-schema.ts</code> and specifies SQLite as the database type. The url is the filename Drizzle will create and in this case, it&rsquo;s <code>sqlite.db</code>.</p><p>Now, run this command in your terminal to sync your code with the database:</p><pre class=" language-shell"><code class="prism  language-shell">npx drizzle-kit push
</code></pre><p>If everything is set up correctly, you should see a success message. Drizzle just created a local <code>sqlite.db</code> file in your project root with all your user, session and account tables.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/drizzle-configuration.png?sfvrsn=8da70ecc_2" title="Drizzle configurations" alt="Drizzle configuration" /></p><h2 id="api-route">API Route</h2><p>Now we need to set up an API route so our frontend can communicate with Better Auth. Create a file named <code>app/api/auth/[...all]/route.ts</code>. This will be the API route that handles all auth requests:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">// app/api/auth/[...all]/route.ts</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> auth <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/lib/auth"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> toNextJsHandler <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"better-auth/next-js"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> GET<span class="token punctuation">,</span> POST <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">toNextJsHandler</span><span class="token punctuation">(</span>auth<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><blockquote><p>If your project uses <code>src/</code>, this goes in <code>src/api/auth/[...all]/route.ts</code>.</p></blockquote><h2 id="auth-client">Auth Client</h2><p>Finally, for our setup, we need a way for our frontend to talk to the backend without writing messy fetch calls.</p><p>Create <code>lib/auth-client.ts</code> file and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> createAuthClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"better-auth/react"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> authClient <span class="token operator">=</span> <span class="token function">createAuthClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  baseURL<span class="token punctuation">:</span> <span class="token string">"http://localhost:3000"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This small utility will give us type-safe methods for signing in, signing up and checking sessions.</p><h2 id="email-and-password-authentication">Email and Password Authentication</h2><p>Now let&rsquo;s build the authentication forms. We&rsquo;ll create two components: one for sign-up and one for sign-in.</p><h3 id="sign-up-form">Sign-up Form</h3><p>Create a file named <code>components/sign-up.tsx</code> and add the following to it:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">// components/sign-up.tsx</span>
<span class="token string">"use client"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> authClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/lib/auth-client"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/navigation"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SignUp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>email<span class="token punctuation">,</span> setEmail<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>password<span class="token punctuation">,</span> setPassword<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> setName<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>isLoading<span class="token punctuation">,</span> setIsLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> signUp <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> authClient<span class="token punctuation">.</span>signUp<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span>
      <span class="token punctuation">{</span>
        email<span class="token punctuation">,</span>
        password<span class="token punctuation">,</span>
        name<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        onRequest<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">"/dashboard"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        onError<span class="token punctuation">:</span> <span class="token punctuation">(</span>ctx<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token function">alert</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>error<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">return</span> <span class="token punctuation">(</span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-4 w-full max-w-md mx-auto mt-10 border border-gray-200 p-6 rounded-lg shadow-sm bg-white<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-xl font-bold text-gray-900<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Create Account<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-sm font-medium text-gray-700<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
        <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span>
        <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span>
        <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setName</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
        <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>John Doe<span class="token punctuation">"</span></span>
        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>border border-gray-300 p-2 rounded focus:outline-none focus:ring-2 focus:ring-black text-black bg-white<span class="token punctuation">"</span></span>
      <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-sm font-medium text-gray-700<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Email<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
        <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span>
        <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>email<span class="token punctuation">}</span></span>
        <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setEmail</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
        <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user@example.com<span class="token punctuation">"</span></span>
        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>border border-gray-300 p-2 rounded focus:outline-none focus:ring-2 focus:ring-black text-black bg-white<span class="token punctuation">"</span></span>
      <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-sm font-medium text-gray-700<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Password<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
        <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span>
        <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>password<span class="token punctuation">}</span></span>
        <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setPassword</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
        <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;<span class="token punctuation">"</span></span>
        <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>border border-gray-300 p-2 rounded focus:outline-none focus:ring-2 focus:ring-black text-black bg-white<span class="token punctuation">"</span></span>
      <span class="token punctuation">/&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

    <span class="token operator">&lt;</span>button
      onClick<span class="token operator">=</span><span class="token punctuation">{</span>signUp<span class="token punctuation">}</span>
      disabled<span class="token operator">=</span><span class="token punctuation">{</span>isLoading<span class="token punctuation">}</span>
      className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token template-string"><span class="token string">`mt-2 p-2 rounded text-white font-medium transition-colors </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
        isLoading
          <span class="token operator">?</span> <span class="token string">"bg-gray-400 cursor-not-allowed"</span>
          <span class="token punctuation">:</span> <span class="token string">"bg-black hover:bg-gray-800"</span>
      <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">}</span>
    <span class="token operator">&gt;</span>
      <span class="token punctuation">{</span>isLoading <span class="token operator">?</span> <span class="token string">"Creating an account..."</span> <span class="token punctuation">:</span> <span class="token string">"Sign Up"</span><span class="token punctuation">}</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span> 
</code></pre><p>This component handles the entire sign-up flow in a single file. If you look closer, you&rsquo;ll see that the heavy lifting is done by <code>authClient.signUp.email()</code>. This function is type-safe and accepts two distinct arguments (the payload and the event handlers):</p><ul><li><strong>The payload</strong>: This is the actual data we&rsquo;re sending. In this case, email, password and name. Since our database schema requires these fields, the client verifies we actually provide them. If we miss a required field, TypeScript will flag it instantly.</li><li><strong>Event handlers</strong>: The second argument is an object that controls the request lifecycle, from the moment we click Sign Up to when we get a result. This is one of the perks of Better Auth because it replaces try/catch blocks with clean event hooks. All we have to do is define what happens at each stage: <code>onRequest</code>, <code>onSuccess</code> and <code>onError</code>, which catches errors and alerts the user in this case.</li></ul><h2 id="sign-in-form">Sign-in Form</h2><p>With our current setup, users can only register. We need to log them in if they&rsquo;re existing users.</p><p>Create a file named <code>components/sign-in.tsx</code> and add the following to it:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//components/sign-in.tsx</span>
<span class="token string">"use client"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> authClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/lib/auth-client"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/navigation"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SignIn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>email<span class="token punctuation">,</span> setEmail<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>password<span class="token punctuation">,</span> setPassword<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">[</span>isLoading<span class="token punctuation">,</span> setIsLoading<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">const</span> signIn <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> authClient<span class="token punctuation">.</span>signIn<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span>
      <span class="token punctuation">{</span>
        email<span class="token punctuation">,</span>
        password<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">{</span>
        onRequest<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
          router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">"/dashboard"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        onError<span class="token punctuation">:</span> <span class="token punctuation">(</span>ctx<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token function">setIsLoading</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token function">alert</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>error<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-4 w-full max-w-md mx-auto mt-10 border border-gray-200 p-6 rounded-lg shadow-sm bg-white<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-xl font-bold text-gray-900<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Sign In<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-sm font-medium text-gray-700<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Email<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span>
          <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>email<span class="token punctuation">}</span></span>
          <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setEmail</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
          <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>user@example.com<span class="token punctuation">"</span></span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>border border-gray-300 p-2 rounded focus:outline-none focus:ring-2 focus:ring-black text-black bg-white<span class="token punctuation">"</span></span>
        <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-sm font-medium text-gray-700<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Password<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span>
          <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>password<span class="token punctuation">"</span></span>
          <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span>password<span class="token punctuation">}</span></span>
          <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setPassword</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span>
          <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;<span class="token punctuation">"</span></span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>border border-gray-300 p-2 rounded focus:outline-none focus:ring-2 focus:ring-black text-black bg-white<span class="token punctuation">"</span></span>
        <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

      <span class="token operator">&lt;</span>button
        onClick<span class="token operator">=</span><span class="token punctuation">{</span>signIn<span class="token punctuation">}</span>
        disabled<span class="token operator">=</span><span class="token punctuation">{</span>isLoading<span class="token punctuation">}</span>
        className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token template-string"><span class="token string">`mt-2 p-2 rounded text-white font-medium transition-colors </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>
          isLoading
            <span class="token operator">?</span> <span class="token string">"bg-gray-400 cursor-not-allowed"</span>
            <span class="token punctuation">:</span> <span class="token string">"bg-black hover:bg-gray-800"</span>
        <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">}</span>
      <span class="token operator">&gt;</span>
        <span class="token punctuation">{</span>isLoading <span class="token operator">?</span> <span class="token string">"Loading..."</span> <span class="token punctuation">:</span> <span class="token string">"Sign In"</span><span class="token punctuation">}</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This component is nearly identical to the sign-up form with one key difference: we only need email and password since we&rsquo;re verifying an existing user, not creating a new one.</p><p>When a user submits the form, Better Auth verifies the credentials against our database. If they match, it automatically creates a session and sets a secure HttpOnly cookie. This cookie persists the login state, so users stay logged in even after refreshing the page.</p><p>Remember when we enabled <code>emailAndPassword: { enabled: true }</code> in the <code>lib/auth.ts</code> file? Better Auth read that configuration and automatically generated this method for us.</p><p>Now let&rsquo;s create dedicated pages for these forms.</p><h2 id="creating-authentication-pages">Creating Authentication Pages</h2><p>Instead of embedding forms on the homepage, we&rsquo;ll create dedicated routes for sign-in, sign-up and a dashboard to verify successful login.</p><h3 id="sign-up-page">Sign-up Page</h3><p>Create a component called <code>app/signup/page.tsx</code> and add the following to it:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//app/signup/page.tsx</span>
<span class="token keyword">import</span> SignUp <span class="token keyword">from</span> <span class="token string">"@/components/sign-up"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> Link <span class="token keyword">from</span> <span class="token string">"next/link"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SignUpPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center min-h-screen bg-gray-50<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>SignUp</span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-6 text-center text-sm text-gray-600<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        Already have an account<span class="token operator">?</span><span class="token punctuation">{</span><span class="token string">" "</span><span class="token punctuation">}</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Link</span>
          <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/signin<span class="token punctuation">"</span></span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-blue-600 font-medium hover:underline<span class="token punctuation">"</span></span>
        <span class="token punctuation">&gt;</span></span>
          Sign In
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Link</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This page wraps the <code>&lt;SignUp /&gt;</code> component and adds a <code>link</code> to sign in.</p><h3 id="sign-in-page">Sign-in Page</h3><p>Create a component called <code>app/signin/page.tsx</code> and add the following to it:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//app/signin/page.tsx</span>
<span class="token keyword">import</span> SignIn <span class="token keyword">from</span> <span class="token string">"@/components/sign-in"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> Link <span class="token keyword">from</span> <span class="token string">"next/link"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">SignInPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center min-h-screen bg-gray-50<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>SignIn</span> <span class="token punctuation">/&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-6 text-center text-sm text-gray-600<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        New <span class="token class-name">here</span><span class="token operator">?</span><span class="token punctuation">{</span><span class="token string">" "</span><span class="token punctuation">}</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Link</span>
          <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/signup<span class="token punctuation">"</span></span>
          <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-blue-600 font-medium hover:underline<span class="token punctuation">"</span></span>
        <span class="token punctuation">&gt;</span></span>
          Create an account
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Link</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This page wraps the <code>&lt;SignIn /&gt;</code> component and adds a link to Sign Up.</p><h3 id="dashboard-page">Dashboard Page</h3><p>We need a destination for users after they log in. For now, let&rsquo;s create a simple static page named <code>app/dashboard/page.tsx</code> and add the following to it:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//app/dashboard/page.tsx</span>
<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Dashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center min-h-screen bg-white text-black<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-3xl font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mt-4 text-gray-600<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>You are successfully logged <span class="token keyword">in</span><span class="token operator">!</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Now you can start your server by running <code>npm run dev</code> and then open <a target="_blank" href="http://localhost:3000">http://localhost:3000</a>.</p><p>You should see a landing page with options to Sign In or Create Account. Follow these steps to test the authentication:</p><ul><li>Click &ldquo;Create Account&rdquo; and create a new user. If it works, you should be redirected to the dashboard.</li><li>To test the login form, you don&rsquo;t need to open another window. Just hit the back button in your browser to return to the landing page. Click &ldquo;Sign In&rdquo; this time, enter the email and password you just used to sign up, and watch it redirect you back to the dashboard.</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/sign-up-and-redirect.gif?sfvrsn=eb3db7f9_2" title="Testing the Sign Up and Redirect flow" alt="Testing the Sign Up and Redirect flow" /></p><h2 id="accessing-user-sessions">Accessing User Sessions</h2><p>Right now, our dashboard just shows static text. We need to make it smart so it displays the actual user&rsquo;s name and email.</p><p>To do this, we use the <code>useSession</code> hook from the <code>auth-client</code>. This hook gives us real-time access to the user&rsquo;s data.</p><p>Update your <code>app/dashboard/page.tsx</code> file with the following:</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//app/dashboard/page.tsx</span>
<span class="token string">"use client"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> authClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/lib/auth-client"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/navigation"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Dashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">:</span> session<span class="token punctuation">,</span> isPending <span class="token punctuation">}</span> <span class="token operator">=</span> authClient<span class="token punctuation">.</span><span class="token function">useSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>isPending<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex min-h-screen items-center justify-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-gray-500<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Loading<span class="token operator">...</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>session<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">"/signin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center min-h-screen gap-4 bg-white text-black<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-2xl font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Welcome back<span class="token punctuation">,</span> <span class="token punctuation">{</span>session<span class="token punctuation">.</span>user<span class="token punctuation">.</span>name<span class="token punctuation">}</span><span class="token operator">!</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-gray-600<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        You are logged <span class="token keyword">in</span> <span class="token keyword">as</span><span class="token punctuation">{</span><span class="token string">" "</span><span class="token punctuation">}</span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-semibold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>session<span class="token punctuation">.</span>user<span class="token punctuation">.</span>email<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

      <span class="token operator">&lt;</span>button
        onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token keyword">await</span> authClient<span class="token punctuation">.</span><span class="token function">signOut</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
            fetchOptions<span class="token punctuation">:</span> <span class="token punctuation">{</span>
              onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">"/signin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span>
        className<span class="token operator">=</span><span class="token string">"px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"</span>
      <span class="token operator">&gt;</span>
        Sign Out
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>We introduced the <code>useSession()</code> hook. This is the bridge between the frontend and the user&rsquo;s session. It gives us access to the current session and keeps it in sync as authentication changes. It returns two key values:</p><ul><li><strong>data</strong>: The session object containing user information (or null if not logged in).</li><li><strong>isPending</strong>: A boolean indicating whether the session is still loading. With this, we&rsquo;re able to show a loading state so the user doesn&rsquo;t see empty content.</li></ul><p>Finally, the Sign Out button calls <code>authClient.signOut()</code>. This function invalidates the session cookie and uses the <code>onSuccess</code> callback to send the user straight back to the login screen.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/authenticated-user-session.gif?sfvrsn=cf86529c_2" title="Displaying the authenticated user&#39;s session data" alt="Displaying the authenticated user&#39;s session data" /></p><h2 id="session-management-and-protected-routes">Session Management and Protected Routes</h2><p>Now that our app works, let&rsquo;s look at what happens under the hood.</p><p>When a user signs in, Better Auth creates a session and stores it as an HttpOnly cookie. Unlike regular cookies that you can read with <code>document.cookie</code>, these are blocked from frontend JavaScript entirely. Only the browser can send them automatically with each request to the server.</p><p>There are two main ways of securing your pages, and they serve different purposes:</p><ul><li><strong>Client-Side Protection</strong>: This is what we implemented in our <code>app/dashboard/page.tsx</code> earlier. We wait for the session to load in the browser, and, if it&rsquo;s missing, we redirect the user to the &ldquo;Sign In&rdquo; page.</li><li><strong>Server-Side Protection (better for security)</strong>: We check the session on the server before the page renders. If there&rsquo;s no valid session, the request gets blocked immediately, and the sensitive page content is never sent to the browser.</li></ul><p>To implement server-side protection, we use middleware. Middleware is code that runs before a page loads. It sits between the user&rsquo;s request and your page, checking conditions and deciding whether to allow access or redirect elsewhere. In Next.js, it runs on the server, so unauthorized users never even download the page.</p><h2 id="protecting-routes-with-middleware">Protecting Routes with Middleware</h2><p>Create a new file named <code>middleware.ts</code> at the same level as your app folder and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token comment">//middleware.ts</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> betterFetch <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@better-fetch/fetch"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Session <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"better-auth/types"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> NextResponse<span class="token punctuation">,</span> <span class="token keyword">type</span> NextRequest <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/server"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">authMiddleware</span><span class="token punctuation">(</span>request<span class="token punctuation">:</span> NextRequest<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">:</span> session <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> betterFetch<span class="token operator">&lt;</span>Session<span class="token operator">&gt;</span><span class="token punctuation">(</span>
    <span class="token string">"/api/auth/get-session"</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      baseURL<span class="token punctuation">:</span> request<span class="token punctuation">.</span>nextUrl<span class="token punctuation">.</span>origin<span class="token punctuation">,</span>
      headers<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        cookie<span class="token punctuation">:</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"cookie"</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">""</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>session<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> NextResponse<span class="token punctuation">.</span><span class="token function">redirect</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span><span class="token string">"/signin"</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> NextResponse<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token punctuation">{</span>
  matcher<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"/dashboard"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre><p>In a nutshell, the config object at the bottom defines the rules of engagement, telling Next.js to strictly apply this security check only to routes starting with <code>/dashboard</code>. When a user attempts to visit the page, the middleware steps in to verify their session first. If the session is missing, it instantly blocks the request and redirects them to the &ldquo;Sign In&rdquo; page, preventing the protected content from reaching the browser.</p><h2 id="refactor-the-dashboard">Refactor the Dashboard</h2><p>Now that the server (middleware) is handling the security, we can simplify our dashboard page. We don&rsquo;t need to redirect from inside the component anymore, but we&rsquo;ll keep the data fetching to show the user&rsquo;s name and email address.</p><pre class=" language-jsx"><code class="prism  language-jsx"><span class="token comment">//app/dashboard/page.tsx</span>
<span class="token string">"use client"</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token punctuation">{</span> authClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@/lib/auth-client"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> useRouter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next/navigation"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">Dashboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">useRouter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token punctuation">:</span> session<span class="token punctuation">,</span> isPending <span class="token punctuation">}</span> <span class="token operator">=</span> authClient<span class="token punctuation">.</span><span class="token function">useSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">if</span> <span class="token punctuation">(</span>isPending<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex min-h-screen items-center justify-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-gray-500<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Loading<span class="token operator">...</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex flex-col items-center justify-center min-h-screen gap-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-2xl font-bold<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>p-4 border rounded shadow-sm bg-white min-w-[300px]<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-gray-600 mb-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
          Signed <span class="token keyword">in</span> <span class="token keyword">as</span><span class="token punctuation">:</span><span class="token punctuation">{</span><span class="token string">" "</span><span class="token punctuation">}</span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>font-semibold text-black<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token punctuation">{</span>session<span class="token operator">?</span><span class="token punctuation">.</span>user<span class="token punctuation">.</span>email<span class="token punctuation">}</span>
          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-xs text-gray-400<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>User ID<span class="token punctuation">:</span> <span class="token punctuation">{</span>session<span class="token operator">?</span><span class="token punctuation">.</span>user<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

      <span class="token operator">&lt;</span>button
        onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token keyword">await</span> authClient<span class="token punctuation">.</span><span class="token function">signOut</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
            fetchOptions<span class="token punctuation">:</span> <span class="token punctuation">{</span>
              onSuccess<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                router<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token string">"/signin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">}</span>
        className<span class="token operator">=</span><span class="token string">"px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"</span>
      <span class="token operator">&gt;</span>
        Sign Out
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Now when we sign out from the app and try to manually visit <a target="_blank" href="http://localhost:3000/dashboard">http://localhost:3000/dashboard</a>, we should be instantly redirected back to the sign-in page. The dashboard will never attempt to render because there&rsquo;s no valid session.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/access-the-dashboard.gif?sfvrsn=32489ceb_2" title="Trying to access the dashboard without a session" alt="Trying to access the dashboard without a session" /></p><h2 id="conclusion">Conclusion</h2><p>At the base level, Better Auth handles the heavy lifting for authentication. The good thing is you aren&rsquo;t reinventing the wheel, and because it works with pretty much everything, you don&rsquo;t even have to rewrite your whole auth setup if you switch frameworks down the line. It just works, so you can focus on your app.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">An Introduction to TanStack DB: A Reactive Data Layer for Modern Web Applications</h4></div><div class="col-8"><p class="u-fs16 u-mb0">In this article, we will build a to-do app to <a target="_blank" href="https://www.telerik.com https://www.telerik.com/blogs/introduction-tanstack-db-reactive-data-layer-modern-web-applications">see TanStack DB in action</a>. We&rsquo;ll cover how to set up collections, what &ldquo;live queries&rdquo; are, and why &ldquo;differential dataflow&rdquo; makes it so fast. We'll also cover when to use TanStack DB and when TanStack Query alone is enough.</p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17316560.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9bf61f7e-e7a4-44d4-aba4-e8c1fc74615b</id>
    <title type="text">Progress Telerik Agentic UI Generator vs. Syncfusion Agentic UI Builder</title>
    <summary type="text">See how the AI-based UI creation tools from devtools powerhouses Progress and Syncfusion stack up when they go head to head.</summary>
    <published>2026-04-08T16:38:33Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17315937/progress-telerik-agentic-ui-generator-vs-syncfusion-agentic-ui-builder"/>
    <content type="text"><![CDATA[<p><span class="featured">See how the AI-based UI creation tools from devtools powerhouses Progress and Syncfusion stack up when they go head to head. </span></p><p>AI-powered code generation has become a significant part of how developers build user interfaces. The <a target="_blank" href="https://survey.stackoverflow.co/2025/ai/#1-ai-tools-in-the-development-process">2025 Stack Overflow Developer Survey</a> found that 84% of developers are now using or planning to use AI tools in their development process.</p><p>Original AI adoption has been through tools like code autocompletion and chat-based assistants, but agentic UI tools take things further. Rather than suggesting the next line of code, they can take a natural language prompt and produce working, styled UI code in seconds.</p><p>However, not all AI generation tools are created equal. The quality of the output, how reliably it builds on the first attempt, how fast it runs and how well it handles things like accessibility and theming can vary quite a bit between tools. These differences matter because a tool that requires a large number of follow-up prompts to correct errors or produce code that doesn&rsquo;t match the original requirements isn&rsquo;t truly saving us time.</p><p>In this article, we&rsquo;ll look at a head-to-head comparison between two agentic UI generation tools: the <strong>Progress Telerik Agentic UI Generator</strong> which includes the <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/ai/agentic-ui-generator/getting-started">Blazor Agentic UI Generator for .NET/Blazor</a> and the <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/getting-started">Kendo UI Generator for Angular and React</a>, and the <a target="_blank" href="https://www.syncfusion.com/explore/agentic-ui-builder/"><strong>Syncfusion Agentic UI Builder</strong></a>. We&rsquo;ll walk through the results of extensive testing across multiple frameworks, covering output quality, performance, stability, accessibility, theming and more.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/progress-vs-syncfusion.jpg?sfvrsn=d281e72b_2" alt="Progress Telerik vs. Syncfusion" /></p><h2 id="how-the-comparison-was-conducted">How the Comparison Was Conducted</h2><p>To make this comparison as fair and thorough as possible, both tools were tested across multiple frameworks using two evaluation approaches.</p><h3 id="blazor.net">Blazor/.NET</h3><p>On the Blazor/.NET side, 23 prompts were run through the Telerik Agentic UI Generator and the Syncfusion Agentic UI Builder. These prompts ranged from simple single-component requests to complex multi-section layouts like e-commerce catalog pages and CMS admin dashboards. Each prompt&rsquo;s output was rated on a 0 to 7 scale:</p><ul><li><strong>0-1</strong>: Doesn&rsquo;t build or crashes with no UI</li><li><strong>2-3</strong>: Partially renders, major sections broken</li><li><strong>4</strong>: Renders but requires multiple prompts to clear build or runtime errors</li><li><strong>5</strong>: Renders but doesn&rsquo;t match prompt requirements in several areas</li><li><strong>6</strong>: Mostly complete, minor issues</li><li><strong>7</strong>: Fully matches requirements</li></ul><p>The evaluation also tracked how many prompts were needed before the output could successfully build and render, as well as the time (in seconds) from initial prompt submission to output generation.</p><h3 id="angular-and-react">Angular and React</h3><p>For Angular and React, a dedicated evaluation was conducted comparing the Kendo Agentic UI Generator against the Syncfusion Agentic UI Builder. Rather than scoring individual prompts, this evaluation focused on a technical comparison of the tools themselves. By running many prompts and examining the context returned by each tool, the analysis compared how each MCP server helps the AI agent arrive at its output across categories like layout, styling, accessibility, documentation and component handling.</p><h2 id="output-quality-and-reliability">Output Quality and Reliability</h2><p>This is the category where the gap between the two tools becomes immediately clear.</p><p>Across the 23 Blazor prompts, the Telerik Agentic UI Generator consistently produced complete, requirement-matching output on the first attempt. The Syncfusion Agentic UI Builder told a different story. It experienced <strong>five failures</strong> where no usable UI was generated at all, and several other prompts that required follow-up prompts to fix errors before the output could even build. Let&rsquo;s look at a couple of examples.</p><h3 id="failures">Failures</h3><p>The very first prompt asked both tools to build a login screen with email/password validation and an admin dashboard with a sidebar menu, key metrics and recent activity.</p><p><strong>Prompt</strong>: <em>&ldquo;I have created an empty application that now needs a login screen and an admin dashboard. Add a login form with email/password fields and validation. After a successful login, redirect to an admin dashboard page featuring a sidebar menu and a main content area displaying key metrics and recent activity.&rdquo;</em></p><p>The Telerik Generator produced a fully working output on the first attempt.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/blazor-agentic-ui-prompt-1.png?sfvrsn=ead93eba_1" alt="Telerik UI Generator produced dashboard" /></p><p>The Syncfusion Builder returned an error along the lines of: <code>Error. This response is truncated because it is too long. Try rephrasing your question.</code> No UI was generated at all.</p><p>The second prompt told both tools to create a monitoring dashboard with system health KPIs, a log stream panel, charts for API response times and requests per service.</p><p><strong>Prompt</strong>: <em>&ldquo;Create a new page using the existing top navigation and footer. In the middle, add 3 rows with 3 responsive columns each. The top row shows system health KPIs for CPU, memory and error counts. The middle rows include a Log Stream panel, a line Chart of API response times and a bar chart of requests per service. The bottom row contains a Deployment History table, an Alerts panel and a list of open tickets.&rdquo;</em></p><p>The Telerik Generator again produced a fully working output on the first pass.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/blazor-agentic-ui-prompt-2.png?sfvrsn=150ecc0b_1" alt="Telerik Generator produced dashboard with graphs" /></p><p>The Syncfusion Builder hit the same truncation error.</p><p>These are the kinds of prompts that represent real-world use cases. A login screen with a dashboard and a monitoring page with multiple data panels aren&rsquo;t unusual requests.</p><h3 id="errors-requiring-follow-ups">Errors Requiring Follow-ups</h3><p>Not every issue is an error during the generation process. Some prompts produced output that needed additional work to get running.</p><p>We fired a prompt to ask for a product catalog page with a responsive layout, product cards, a filtering toolbar and expandable detail views. The Telerik Generator handled it cleanly on the first attempt. The Syncfusion Builder hit an error about a missing component parameter and needed a second follow-up prompt to fix it.</p><p><strong>Prompt</strong>: <em>&ldquo;Create a product catalog page with a responsive CSS layout. The layout should display product cards. Add a toolbar with filtering options, and expandable detail view for each product that work seamlessly on mobile, tablet, and desktop.&rdquo;</em></p><p>The first output by the Telerik Blazor generator:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/blazor-agentic-ui-prompt-4.png?sfvrsn=5b3d958b_1" alt="Telerik Blazor AI UI Generator produced product catalog" /></p><p>The final output from Syncfusion, after the necessary follow-up prompt:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/syncfusion-agentic-ui-prompt-4.png?sfvrsn=a91f64f0_1" alt="Syncfusion product catalog" /></p><p>The Syncfusion Builder&rsquo;s errors in these cases often involved referencing component parameters that don&rsquo;t exist, which may suggest the tool&rsquo;s AI agent is working with less reliable component information during generation.</p><h3 id="partial-results">Partial Results</h3><p>Not all issues showed up as crashes or errors needing follow-ups. Let&rsquo;s look at an example of a prompt that was made which involved a theming request.</p><p><strong>Prompt</strong>: <em>&ldquo;Generate a custom theme for a corporate blue and green color scheme with high contrast accessibility requirements.&rdquo;</em></p><p>The Telerik Agentic UI Generator created the output matching the requirements.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/blazor-agentic-ui-prompt-13.png?sfvrsn=ed53d11a_1" alt="Telerik Agentic UI generator blue n=and green" /></p><p>The Syncfusion Builder did create a custom theme, but the expected theme wasn&rsquo;t applied to the output.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/syncfusion-agentic-ui-prompt-13.png?sfvrsn=eaac1328_1" alt="Syncfusion green and blue missing" /></p><h3 id="the-bigger-picture">The Bigger Picture</h3><p>Even when we set aside the failures and only look at prompts where the Syncfusion Builder produced something usable, the outputs still frequently fell short of the original requirements.</p><p>On the Angular and React side, the findings were consistent. While the final rendered UI sometimes appeared similar at a glance, the most significant differences were in how each tool&rsquo;s MCP server helped the agent arrive at that output. The Kendo Generator&rsquo;s tools consistently delivered more relevant, targeted context to the AI agent, filtering results to what was actually needed for the prompt. The Syncfusion Builder&rsquo;s tools returned broader, less filtered responses, often including full component prop lists and governance rules regardless of what was asked.</p><h2 id="performance">Performance</h2><p>Speed matters when we&rsquo;re iterating on UI designs with AI. Waiting several minutes for a complex layout to generate can significantly slow down the development flow.</p><p>Across the Blazor evaluation, the Telerik Generator was consistently faster. The speed advantage was most noticeable on complex layout prompts. For example, on a complex e-commerce catalog page with filtering, sorting, responsive breakpoints and pagination, the Telerik Generator completed in about 799 seconds while the Syncfusion Builder took 908 seconds.</p><p>There was one outlier where Syncfusion was notably faster (a simpler single-component case), but across the full set of 23 prompts, the speed advantage consistently favored Telerik.</p><h2 id="accessibility">Accessibility</h2><p>Accessibility is often treated as an afterthought, but for enterprise applications, it&rsquo;s a requirement, not a nice-to-have!</p><p>The Kendo Generator includes a dedicated accessibility tool (<a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/prompt-library#accessibility-assistant">#kendo_accessibility_assistant</a>) that retrieves WCAG 2.2 Level AA guidelines as a mandatory step in the generation process. It also fetches component-specific accessibility requirements for each component used in the output. This leads to every piece of generated UI having accessibility considerations baked in from the start.</p><p>From what we can gather, and at the time of writing, the Syncfusion Builder doesn&rsquo;t have a strong accessibility tool like the <code>#kendo_accessibility_assistant</code>.</p><h2 id="theming-layout-and-documentation">Theming, Layout and Documentation</h2><p>Beyond output quality and speed, the Angular and React evaluation revealed some important architectural differences in how each tool handles theming, layout and documentation.</p><h3 id="theming">Theming</h3><p><strong>Theming</strong> is more than color selection. The Kendo UI Generator produces a complete design system from a single prompt, including colors, typography, spacing scales, border radius, elevation and shadows. The Syncfusion Builder&rsquo;s style tool is limited to component state colors and semantic colors. Typography, spacing and elevation aren&rsquo;t reachable through it, so the generated output may look right in terms of color but lack the design coherence that holds a full application together.</p><h3 id="layout">Layout</h3><p><strong>Layout</strong> is also where a notable dependency difference comes in. The Kendo UI Generator uses a self-contained layout system built on its own CSS utilities, introducing no external dependencies. The Syncfusion Builder relies on <a target="_blank" href="https://tailwindcss.com/">Tailwind CSS</a>, injected into every project, and works from a catalog of 265+ prebuilt blocks. When the desired layout doesn&rsquo;t match a catalog entry, the result approximates what was asked for.</p><p>For teams already using Tailwind, this might feel natural but for projects on a different CSS framework (<a target="_blank" href="https://material.angular.dev/">Angular Material</a>, for example), adding Tailwind on top can introduce style conflicts. This can also be a potential blocker for organizations that restrict third-party dependencies.</p><p>Responsiveness follows a similar split: the Kendo UI Generator has a dedicated phase with explicit breakpoint reasoning, while the Syncfusion Builder inherits responsive behavior from Tailwind&rsquo;s defaults without any first-class responsive logic of its own.</p><h3 id="documentation">Documentation</h3><p><strong>Documentation</strong> plays a bigger role in output quality than it might seem, since it directly shapes how well the AI agent understands the components it&rsquo;s generating. The Kendo UI Generator injects relevant documentation chunks directly into every component tool call, including API references with full type signatures. The Syncfusion Builder treats documentation as a separate tool, triggered only by specific keywords. At the time of the evaluation, results from this tool were inconsistent and the service had been unavailable for over 24 hours for both Angular and React.</p><p>One other important observation from the Angular and React evaluation: the Kendo UI Generator customizes its workflow per prompt using confidence scores, including only the tools and instructions relevant to each request. The Syncfusion Builder runs the same fixed sequence for most prompts regardless of complexity, and its component tools return the full prop list and approximately 250 lines of governance rules on every call. The result is that <strong>the Kendo UI agent operates on targeted, preprocessed context while the Syncfusion agent carries a heavier payload for every request</strong>.</p><h2 id="wrap-up">Wrap-up</h2><p>The comparison across Blazor, Angular and React tells a consistent story with these agentic UI generators. The Progress Telerik Agentic UI Generator outperforms the Syncfusion Agentic UI Builder across output quality, reliability, performance, accessibility, theming, layout and documentation.</p><p>On the Blazor side, the Telerik Generator had zero full failures across 23 prompts while Syncfusion had five, along with a consistent speed advantage on complex prompts and more reliable first-pass output. On the Angular and React side, the advantages show up in the underlying architecture, where the Kendo Generator&rsquo;s tools consistently deliver cleaner, more relevant context to the AI agent.</p><hr /><blockquote><p>Get started with AI-empowered development. The <a target="_blank" href="https://www.telerik.com/mcp-servers">Kendo UI and Telerik AI tools</a> are ready. And they come as part of the free trial of the component libraries.</p><br /><p><a href="https://www.telerik.com/mcp-servers#how-to-try" class="Btn" target="_blank">Try Now</a></p></blockquote><hr /><p>For more information on the Progress Telerik Agentic UI Generator, check out the following resources:</p><ul><li><a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/ai/agentic-ui-generator/getting-started">Telerik Agentic UI Generator for Blazor</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/getting-started">Kendo UI Generator for Angular</a></li><li><a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/getting-started">Kendo UI Generator for React</a></li></ul><p>The full comparison data referenced in this article is available for download:</p><ul><li><a target="_blank" href="https://www.telerik.com/docs/default-source/blogs-docs//syncfusion-builder-vs-telerik-generator-comparison.xlsx">Telerik UI for Blazor Agentic UI Generator vs Syncfusion Agentic UI Builder Comparative Evaluation (XLSX)</a></li><li><a target="_blank" href="https://www.telerik.com/docs/default-source/blogs-docs//syncfusion-ui-builder-vs-kendo-ui-generator.xlsx">Kendo Agentic UI Generator vs Syncfusion Agentic UI Builder (XLSX)</a></li></ul><hr /><h2 id="summary-faqs">Summary FAQs</h2><h3 id="what-do-these-tools-do">What Do These Tools Do?</h3><h4 id="what-telerik-agentic-ui-generator-is">What Telerik Agentic UI Generator Is</h4><p>According to the <a href="https://www.telerik.com/mcp-servers" target="_blank">Progress Telerik website</a>, the Telerik Agentic UI Generator for Telerik and Kendo UI libraries is an intelligent development tool delivered through the Model Context Protocol (MCP) Server that enables UI generation from natural language prompts. Once configured and authenticated, you can use the Agentic UI Generator tool (#telerik_ui_generator) together with the available specialized MCP assistants.</p><h4 id="what-syncfusion-agentic-ui-builder-is">What Syncfusion Agentic UI Builder Is</h4><p>According to the <a target="_blank" href="https://www.syncfusion.com/explore/agentic-ui-builder/">Syncfusion site</a>, Syncfusion&rsquo;s Agentic UI Builder is designed to work inside an IDE to generate UIs, dashboards or pages with Syncfusion components with natural language commands. Syncfusion uses MCP integration to access component APIs, prebuilt UI blocks, styling configurations, icon libraries and code generation.</p><h3 id="why-does-mcp-matter-in-both-products">Why Does MCP Matter in Both Products?</h3><p>Both brands&rsquo; tools use Model Context Protocol (MCP) servers to connect AI applications directly to the software specs for the individual library. This means that the AI is informed on component documentation, best practices, workflows and tooling provided by the UI library, so the AI can create a user interface more in line with how the library was designed to work. It should result in better outputs than if an AI were cobbling together an interface without guidance.</p><p>Learn more about the individual MCP servers:</p><ul><li><a href="https://www.telerik.com/blazor-mcp-servers" target="_blank">Telerik UI for Blazor MCP Servers</a></li><li><a href="https://www.telerik.com/angular-mcp-servers" target="_blank">Kendo UI for Angular MCP Servers</a></li><li><a href="https://www.telerik.com/react-mcp-servers" target="_blank">KendoReact MCP Servers</a></li><li><a target="_blank" href="https://ej2.syncfusion.com/react/documentation/mcp-server/overview">Syncfusion EJ2 React MCP Server</a></li><li><a target="_blank" href="https://ej2.syncfusion.com/angular/documentation/mcp-server/overview">Syncfusion EJ2 Angular MCP Server</a></li><li><a target="_blank" href="https://blazor.syncfusion.com/documentation/mcp-server/overview">Syncfusion Blazor MCP Server</a></li></ul><h3 id="are-prompt-libraries-included">Are Prompt Libraries Included?</h3><h4 id="telerik-and-kendo-ui-generator-prompt-libraries">Telerik and Kendo UI Generator Prompt Libraries</h4><p>Yes! Prompt libraries are included for the Kendo UI and Telerik AI UI Generator tools:</p><ul><li>Blazor: <a target="_blank" href="https://www.telerik.com/blazor-ui/documentation/ai/agentic-ui-generator/prompt-library">https://www.telerik.com/blazor-ui/documentation/ai/agentic-ui-generator/prompt-library</a></li><li>React: <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/getting-started">https://www.telerik.com/kendo-react-ui/components/ai-tools/agentic-ui-generator/getting-started</a></li><li>Angular: <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/prompt-library">https://www.telerik.com/kendo-angular-ui/components/ai-tools/agentic-ui-generator/prompt-library</a></li></ul><h4 id="syncfusion-agentic-ui-builder-prompt-libraries">Syncfusion Agentic UI Builder Prompt Libraries</h4><p>Yes! Prompt libraries are included for the Syncfusion Agentic UI Builder tools:</p><ul><li>Blazor: <a target="_blank" href="https://blazor.syncfusion.com/documentation/mcp-server/agentic-ui-builder/prompt-library">https://blazor.syncfusion.com/documentation/mcp-server/agentic-ui-builder/prompt-library</a></li><li>React: <a target="_blank" href="https://ej2.syncfusion.com/react/documentation/mcp-server/agentic-ui-builder/prompt-library">https://ej2.syncfusion.com/react/documentation/mcp-server/agentic-ui-builder/prompt-library</a></li><li>Angular: <a target="_blank" href="https://ej2.syncfusion.com/angular/documentation/mcp-server/agentic-ui-builder/prompt-library">https://ej2.syncfusion.com/angular/documentation/mcp-server/agentic-ui-builder/prompt-library</a></li></ul><img src="https://feeds.telerik.com/link/10827/17315937.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:aa64f913-fece-4c56-bbb5-5397e3128e02</id>
    <title type="text">What We’ve Learned Designing Dev Tools in the Age of AI</title>
    <summary type="text">AI didn’t just change how we write code. It changed how we build products. See what four lessons Progress Telerik and Kendo UI engineering and product teams have learned.</summary>
    <published>2026-04-08T16:13:28Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Alyssa Nicoll </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17315927/what-weve-learned-designing-dev-tools-ai"/>
    <content type="text"><![CDATA[<p><span class="featured">AI didn&rsquo;t just change how we write code. It changed how we build products.</span></p><p>Over the last year, AI coding agents have gone from glorified autocomplete to autonomous systems capable of scaffolding entire features.</p><p>As our VP of Product put it:</p><blockquote><p>&ldquo;The speed of software engineering is no longer the bottleneck.&rdquo;<br />&mdash; Genady Sergeev, VP Product, Progress</p></blockquote><p>If that&rsquo;s true&mdash;and it increasingly feels like it is&mdash;then something fundamental has shifted.</p><p>The constraint isn&rsquo;t typing. It&rsquo;s judgment.</p><p>These lessons come directly from conversations with our engineering and product teams. Across many of interviews and internal reflections, four themes emerged.</p><h2 id="lesson-1-ai-is-best-at-getting-us-started">Lesson #1: AI Is Best at Getting Us Started</h2><p>One of our product leads described the shift this way:</p><blockquote><p>&ldquo;AI made refactoring and handling technical debt much faster, allowing us to focus on market features. Validating ideas, creating POCs and writing tests are also areas where we&rsquo;ve seen huge impact. The whole development lifecycle changed, and it opened the door for exploration and innovation.&rdquo;<br />&mdash; Yoana Kalaydzhieva, Senior Manager, Software Engineering, Progress</p></blockquote><p>AI lowers the cost of starting. It accelerates refactoring. It scaffolds tests. It makes proof-of-concepts nearly frictionless.</p><p>But our engineers were equally honest:</p><blockquote><p>&ldquo;AI is better at starting than finishing.&rdquo;<br />&mdash; Stefan Mariyanov, Senior Product Manager, Progress</p></blockquote><p>AI gives you momentum. It doesn&rsquo;t give you judgment.</p><p>What we realized is that developers don&rsquo;t actually want magic answers&mdash;they want orientation. They want something to react to, refine and push against.</p><p>That changed how we thought about AI in our tools. Instead of &ldquo;generate and disappear,&rdquo; we moved toward:</p><ul><li>Iterative workflows</li><li>Clear stopping points</li><li>Explicit human decision moments</li></ul><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/what-ai-unlocks.png?sfvrsn=7ccd81b4_2" alt="What AI unlocks: refactoring, tech debt, POCs, tests, exploration" /></p><p>AI made refactoring and technical debt much faster, allowing teams to focus on market features. Validating ideas, creating POCs and writing tests also saw huge impact.</p><p>And what that really opened up was <em>exploration</em>.</p><h2 id="lesson-2-clever-≠-correct">Lesson #2: Clever &ne; Correct</h2><p>Then we ran into something humbling.</p><p>AI-generated code often looks clean, structured and convincing. The formatting is good. The structure feels familiar. The solution appears reasonable at first glance. But many times it&rsquo;s simply wrong.</p><p>As one of our engineers put it:</p><blockquote><p>&ldquo;Code often looks right but breaks in subtle ways.&rdquo;<br />&mdash; Stefan Mariyanov, Senior Product Manager, Progress</p></blockquote><p>That&rsquo;s the danger. It sounds right.</p><p>Kathryn Grayson Nanz framed this beautifully:</p><blockquote><p>&ldquo;I&rsquo;d encourage teams to learn how AI actually works instead of treating it like a black box. Even a high-level understanding&mdash;how models predict the next word, how they gain context&mdash;demystifies the magic.&rdquo;<br />&mdash; Kathryn Grayson Nanz, Senior Developer Advocate, Progress</p></blockquote><p>The key thing to understand is that AI doesn&rsquo;t understand truth. It predicts plausibility. It generates the next token based on patterns it has learned.</p><p>And when tools are confidently wrong, trust erodes fast.</p><p>For developer tools, that&rsquo;s a serious problem. Developers rely on their tools to accelerate their work, not introduce hidden uncertainty. If AI suggestions consistently require double-checking or rewriting, the productivity gains disappear.</p><p>That realization forced us to rethink how AI should be integrated into the products we build.</p><p>Instead of asking AI to guess what developers might want, we started focusing on giving it better context. Rather than generating output in isolation, we began connecting AI directly to the frameworks, component libraries and development tools our users already rely on.</p><p>This is where things like our MCP server and product-aware AI tooling come in. By allowing assistants to access real project structure, real component APIs and real framework constraints, the output becomes grounded in the actual environment the developer is working in.</p><p>In other words, the goal isn&rsquo;t to make AI more clever. The goal is to make it more reliable.</p><h3 id="what-this-changed-for-us">What This Changed for Us</h3><p>For instance, in the Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/ai-tools">Angular library AI tools</a>, that meant building <strong>product-aware AI</strong> that understands the capabilities and APIs of the components in our libraries. It meant adding <strong>framework-aware constraints</strong> so generated code follows Angular patterns instead of generic JavaScript assumptions (or even outdate Angular patterns). And it meant leaning into <strong>opinionated scaffolding</strong>, where AI helps developers start with valid structures instead of forcing them to repair generated output afterward.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/what-this-changed-for-us.png?sfvrsn=4f624aa8_2" alt="What this changed for us: product-aware AI, framework-aware constraints, opinionated scaffolding better than open-ended guessing. Context beats clevernesss." /></p><p>What we realized is that the gap wasn&rsquo;t intelligence, it was context. AI could generate something that looked clever, but without grounding in real tools and constraints, it still left developers doing the hard part.</p><p>So we shifted the question. Instead of asking how to make AI more impressive, we started asking how to make it more trustworthy. And once we framed it that way, the answer showed up everywhere: context beats cleverness.</p><h2 id="lesson-3-fast-isn’t-free">Lesson #3: Fast Isn&rsquo;t Free</h2><p>Speed came with a cost.</p><p>As we started integrating AI more deeply into developer workflows, we began to see both the upside and the tradeoffs more clearly. The productivity gains were real, but so were the risks.</p><p>One of our VPs of Product reflected on this directly:</p><blockquote><p>&ldquo;Using CLI agents successfully requires certain skills and practice. The improvement rate is so fast that observations from 3&ndash;4 months ago are already outdated.&rdquo;<br />&mdash; Genady Sergeev, VP Product, Progress</p></blockquote><p>That pace of change creates a new kind of pressure. Not just to adopt AI, but to continuously relearn how to use it well.</p><p>And we observed something else happening inside teams.</p><blockquote><p>&ldquo;AI helps experienced developers master their productivity&mdash;it&rsquo;s impressive. But less experienced developers might over-trust AI-generated code, which leads to pull requests that require much deeper senior review.&rdquo;<br />&mdash; Yoana Kalaydzhieva, Senior Manager, Software Engineering, Progress</p></blockquote><p>In other words, AI doesn&rsquo;t just accelerate output. It amplifies behavior.</p><p>For experienced developers, that can mean faster iteration and deeper focus. For less experienced developers, it can mean moving quickly in the wrong direction&mdash;and creating more work downstream.</p><p>That&rsquo;s when the real cost of speed started to show up. AI makes output cheaper, but not ownership. It accelerates mistakes just as efficiently as it accelerates success. And when everything moves faster, the cost of catching issues later increases. So we changed how we approached tooling.</p><p>Instead of optimizing purely for generation speed, we started designing for <strong>accountability alongside acceleration</strong>. That meant introducing clear checkpoints where human review is expected, giving teams control over the models and endpoints they use, and enabling every AI-generated action to be traced, inspected and understood.</p><p>We also shifted our mindset around AI output itself. Instead of treating it as a near-final draft, we began treating it as <strong>untrusted input</strong>&mdash;something useful, but something that still requires validation.</p><h3 id="demo-highlight-observability-as-accountability">Demo Highlight: Observability as Accountability</h3><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/ai-observability-program.png?sfvrsn=1cc9aa2c_2" alt="Join the Progress AI Observability Platform Early Access Program" /></p><p>This is where our <a target="_blank" href="https://www.telerik.com/ai-observability-platform">AI Observability Platform</a> became part of the solution. Not to &ldquo;watch code,&rdquo; but to watch reasoning.</p><p>We wanted to make it possible to see what the model did, how it arrived at a result and why certain decisions were made along the way. Because if AI is going to operate inside real workflows, its behavior can&rsquo;t be opaque. Visibility becomes a requirement, not a bonus.</p><p>If AI accelerates execution, then visibility has to scale with it. Because speed, on its own, isn&rsquo;t the goal. Speed is only valuable when accountability scales alongside it.</p><h2 id="lesson-4-ai-belongs-in-the-product—thoughtfully">Lesson #4: AI Belongs in the Product&mdash;Thoughtfully</h2><p>This one surprised us most.</p><p>One internal reflection stood out:</p><blockquote><p>&ldquo;What surprised me most is how quickly the attention span of our customers dropped, and how much the evaluation process changed.&rdquo;<br />&mdash; Yoana Kalaydzhieva, Senior Manager, Software Engineering, Progress</p></blockquote><p>Evaluation cycles compressed, tolerance for friction disappeared, and that shift affects end users too. Users struggle with:</p><ul><li>Complex data</li><li>Dense interfaces</li><li>Discoverability</li></ul><p>AI isn&rsquo;t a replacement UI. It&rsquo;s an augmentation layer.</p><h3 id="demo-highlight-ai-inside-the-interface">Demo Highlight: AI Inside the Interface</h3><p>Lesson #4 shows up very clearly in how we&rsquo;ve approached AI within the Kendo UI Grid&mdash;not as a replacement for the interface, but as a layer that makes it easier to use.</p><p>Instead of hiding the system behind a prompt box, we&rsquo;ve focused on keeping the structure intact: columns, grouping, filtering and data visibility all remain explicit and inspectable. AI becomes a way to express intent faster&mdash;&ldquo;group by this,&rdquo; &ldquo;filter that&rdquo;&mdash;while still grounding every action in the underlying UI. You can explore the full capabilities here: <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/smart-grid">Angular Smart Grid</a>.</p><p>What this means in practice is that the grid doesn&rsquo;t disappear when AI is introduced&mdash;it becomes more approachable. Users can move faster without losing their bearings, because every change is visible, reversible and tied to real UI controls.</p><p>That balance is the point: reducing cognitive load without sacrificing control. Rather than asking users to trust a black box, the grid invites them to collaborate with it&mdash;AI helps initiate actions, but the interface remains the source of truth.</p><h2 id="what-we’re-really-learning">What We&rsquo;re Really Learning</h2><p>Over time, these lessons started to feel less like separate observations and more like different edges of the same shape. AI is incredibly effective at helping us begin, but it doesn&rsquo;t carry the responsibility of finishing. It can produce outputs that look convincing, but without context, that confidence can be misplaced. And while it dramatically increases speed, it also raises the cost of mistakes when that speed isn&rsquo;t paired with visibility and accountability.</p><p>Even when we bring AI into the product itself, the goal isn&rsquo;t to replace the interface&mdash;it&rsquo;s to make it easier to use without taking control away from the user.</p><p>What this ultimately points to is a deeper shift in how we think about building tools. AI changes how quickly we can move, but it doesn&rsquo;t change who owns the outcome. Developers still make the decisions. Teams still carry the consequences. So the goal can&rsquo;t just be faster output&mdash;it has to be confidence in what we&rsquo;re building, even as everything around us accelerates.</p><h2 id="let’s-talk-about-it">Let&rsquo;s Talk About It</h2><p>We&rsquo;re hosting a live <strong>Developer Roundtable</strong> on <strong>April 15 at 9 a.m. PT</strong>, where we&rsquo;re unpacking all of this in real time&mdash;no slides, no polished answers, just honest conversation with developers in the trenches.</p><p>We&rsquo;ll be digging into questions like:</p><ul><li>Where has AI actually helped you move faster&mdash;and where has it created more work?</li><li>How are you validating AI-generated code today?</li><li>What guardrails feel necessary right now?</li><li>Where has AI in tools felt genuinely helpful &hellip; and where has it felt forced?</li></ul><p>Join us on the Progress Labs Discord:</p><p><a target="_blank" href="https://discord.gg/bUhBHmz6?event=1479492929411092562"> Come share what&rsquo;s working, what&rsquo;s breaking and what still feels unresolved.</a></p><p>Because the truth is&mdash;we&rsquo;re all still figuring this out. And the best insights aren&rsquo;t coming from headlines. They&rsquo;re coming from conversations like this.</p><img src="https://feeds.telerik.com/link/10827/17315927.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:260a4048-454d-49d1-adbc-60f4cc32775d</id>
    <title type="text">Workflows in the Age of AI: How Design &amp; Development Workflows Changed in 2025—and What Comes Next</title>
    <summary type="text">We asked 200+ respondents across design and development about the ways they’re leveraging new AI capabilities in their work. Download the full report and check out these top takeaways.</summary>
    <published>2026-04-07T19:31:29Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17315391/workflows-age-ai-how-design-development-changed-what-comes-next"/>
    <content type="text"><![CDATA[<p><span class="featured">We asked 200+ respondents across design and development about the ways they&rsquo;re leveraging new AI capabilities in their work. Download the full report and check out these top takeaways.</span></p><p>AI may be a normal part of daily life now, but the reality of implementing AI-powered tooling in the workplace is not so simple. Teams are moving faster, but workflows are becoming messier, more complex and more challenging to scale. AI is improving productivity at the individual and small-team level&mdash;but those gains aren't necessarily translating into consistent, scalable workflows across teams.</p><p>How do we know? <a target="_blank" href="https://www.telerik.com/blogs/designer-developer-collaboration-age-ai">In our 2025 Designer-Developer Collaboration survey</a>, we asked 200+ respondents across design and development (including hybrid and leadership roles, as well) about the ways they&rsquo;re leveraging new AI capabilities in their work. You can <a target="_blank" href="https://www.telerik.com/ai-design-development-workflows-report-2025">download the full report</a> to see everything, but these were our top takeaways:</p><h2 id="-1-ai-is-the-default-maturity-is-not-">1. AI Is the Default&mdash;Maturity Is Not.</h2><p>People are using AI: 84% of people, to be precise (or at least, 84% of the people who filled out our survey). This was divided among folks who are all in&mdash;taking &ldquo;an AI-first approach across [their] entire process&rdquo;&mdash;and folks who have AI tools regularly integrated or in experimental phases in their current workflows. Only 16% said they were avoiding the use of AI tools entirely.</p><p>The challenge is no longer simply adoption, but rather how to turn AI usage into a reliable, repeatable process.</p><h2 id="-2-speed-is-real-trust-is-conditional-">2. Speed Is Real. Trust Is Conditional.</h2><p>There&rsquo;s a lot of back-and-forth right now on whether AI <em>actually</em> delivers its promises of improved productivity. </p><p>Our respondents were also mixed; a little over half said it was &ldquo;moderately positive&rdquo;, helping somewhat but with some limitations. And 33% found it to have a negative effect&mdash;split between being neutral (having no impact), moderately negative (added more work than saved), or significantly negative (disrupted workflow significantly). Only 16% felt strongly positive about it, claiming major time savings and quality improvements.</p><p>Speed isn&rsquo;t an issue, but consistency is. We need tools and systems that can be trusted to produce reliable results in order to scale them across teams and businesses.</p><h2 id="-3-collaboration-friction-still-matters-">3. Collaboration Friction Still Matters</h2><p>Despite all the new tooling AI has made available to us in the form of quick prototyping, design-to-code features and vibe coding &hellip; it hasn&rsquo;t fixed the core design-dev &ldquo;handoff&rdquo; problem. </p><p>Only 21% of our participants reported &ldquo;smooth handoffs and minimal issues&rdquo; when we asked how they would rate the efficiency of the design implementation process. When things fall through the cracks, slower time-to-market (42%), rework (33%) and wasted time (36%) were listed as the top three consequences&mdash;all significant pain points at a time when we&rsquo;re shipping new products and features faster than ever before.</p><p>This is where structured systems, shared standards and better-integrated tooling become critical.</p><h2 id="-4-2026-is-about-operational-execution-not-experimentation-">4. 2026 Is About Operational Execution&mdash;Not Experimentation</h2><p>People&rsquo;s top priority coming into 2026 was the effective implementation of AI tools (39%), closely followed by building hybrid skillsets across their teams (29%). AI is reshaping the workplace, and both teams and individual roles are stretching to incorporate the disruption and turn it into something valuable. </p><p>This shift makes the path forward clear: AI needs to move from ad-hoc usage into systems, standards-aware workflows and production-ready processes that can scale. Whether or not that&rsquo;s possible is something I suppose we&rsquo;ll have to wait until next year&rsquo;s survey to find out!</p><h2 id="-want-the-full-picture-">Want the Full Picture?</h2><p>You can <a target="_blank" href="https://www.telerik.com/ai-design-development-workflows-report-2025">download the full report</a> to see the answers to all the questions, including extra data analysis and interesting cross-sections. We think there&rsquo;s a lot to learn, and the report is packed with helpful tips&mdash;as well as our own predictions on what to watch next.</p><p>Finally, if you took the survey: <strong>thank you so much!</strong> We genuinely enjoyed going through all the responses and seeing what everyone had to say. Keep an eye out for next year&rsquo;s survey, and&mdash;until then&mdash;keep building and experimenting. We can&rsquo;t wait to see what you&rsquo;ve been working on!</p><p><a class="Btn" target="_blank" href="https://www.telerik.com/ai-design-development-workflows-report-2025">See Full Report</a></p><img src="https://feeds.telerik.com/link/10827/17315391.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:d364128b-f493-4804-8555-fe37bca196fa</id>
    <title type="text">Blazor Basics: Building Responsive Blazor Apps with CSS Media Queries</title>
    <summary type="text">Responsive design is the reason applications feel polished and user-friendly. Learn how to build responsive Blazor web applications using CSS media queries.</summary>
    <published>2026-04-06T20:25:23Z</published>
    <updated>2026-05-10T18:36:22Z</updated>
    <author>
      <name>Claudio Bernasconi </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17314706/blazor-basics-building-responsive-blazor-apps-css-media-queries"/>
    <content type="text"><![CDATA[<p><span class="featured">Responsive design is the reason applications feel polished and user-friendly. Learn how to build responsive Blazor web applications using CSS media queries.</span></p><p>Today, we will learn how to build responsive Blazor web applications. Responsive design is the reason applications feel polished and user-friendly.</p><p>We will first learn the basics of responsive design and how to use CSS media queries to implement responsive layouts. Next, we will learn about what we should and should not do in our Blazor logic (C# code).</p><p>You can <a target="_blank" href="https://github.com/claudiobernasconi/BlazorMediaQueries">access the code used in this example on GitHub</a>.</p><h2 id="what-is-responsive-design-for-blazor-apps">What Is Responsive Design for Blazor Apps?</h2><p>Blazor provides a user interface rendering engine (Razor components) that lets us implement behavior using C#. Blazor components consist of an HTML and CSS template combined with C# interaction logic.</p><p>In modern web development, we want to utilize <strong>CSS media queries</strong> to let our web applications adapt to different screen sizes:</p><ul><li>Small screens, such as mobile phones</li><li>Medium screens, such as tablets</li><li>Large screens, such as desktop computers</li><li>(optional) Ultra-wide monitors</li></ul><p>Best practices for general web development also apply to implementing Blazor web applications.</p><h2 id="css-media-queries-in-blazor-components">CSS Media Queries in Blazor Components</h2><p>Let&rsquo;s learn about <strong>CSS media queries</strong> using a practical example, a Blazor web application generated from the .NET 10 Blazor Web App project template.</p><p>The <code>NavMenu</code> component contains a menu toggler. How it appears on the screen differs completely between a larger and a smaller screen.</p><p><img title="Blazor Web App: No Nav Button" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/nav-button-not-visible-on-desktop.png?sfvrsn=257d381_2" alt="A browser with a running Blazor web appliation on desktop. The nav button is not visible." /></p><p>For a larger screen, the menu is always visible on the left. For a smaller screen, the menu is collapsed, and the menu toggler is visible. The menu only appears if the user presses the hamburger menu button.</p><p>The HTML definition in the <code>NavMenu</code> component looks like this:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Navigation menu<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>navbar-toggler<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>It&rsquo;s an input element with the <code>navbar-toggler</code> CSS class attached.</p><p>Let&rsquo;s look at the CSS code defining that behavior:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.navbar-toggler</span> </span><span class="token punctuation">{</span>
    <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
    <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">3.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">2.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span>
    <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
    <span class="token property">top</span><span class="token punctuation">:</span> <span class="token number">0.5</span>rem<span class="token punctuation">;</span>
    <span class="token property">right</span><span class="token punctuation">:</span> <span class="token number">1</span>rem<span class="token punctuation">;</span>
    <span class="token property">border</span><span class="token punctuation">:</span> <span class="token number">1</span>px solid <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token number">255</span>, <span class="token number">255</span>, <span class="token number">255</span>, <span class="token number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token property">background</span><span class="token punctuation">:</span> <span class="token url">url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")</span> no-repeat center/<span class="token number">1.75</span>rem <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token number">255</span>, <span class="token number">255</span>, <span class="token number">255</span>, <span class="token number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>This CSS class definition defines the visual appearance of the menu toggler. It includes an inline SVG to define the three horizontal lines and a few spacing definitions.</p><p>As shown in the image above, on larger screens, this nav menu button is not visible. Let&rsquo;s look at the CSS code that implements this behavior.</p><pre class=" language-css"><code class="prism  language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 641px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector"><span class="token class">.navbar-toggler</span> </span><span class="token punctuation">{</span>
        <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>@media</code> syntax introduces the <strong>CSS media query</strong> feature. In the parentheses, we can implement a filter. In this case, we want all CSS definitions placed inside the curly braces to apply in case the filter condition evaluates to true. In this case, the minimum width of the rendered web page is expected to be 641 pixels.</p><p>In other words: When the page is at least 641 pixels in width, we want the CSS definitions inside the curly braces to be applied. In this definition, we set the <code>display</code> property to <code>none</code> for the <code>navbar-toggler</code> CSS class.</p><p><strong>Learning:</strong> Using CSS media queries, we can implement conditional rules based on a filter criterion. For example, we can add additional CSS definitions for existing CSS classes.</p><p><strong>Hint:</strong> There are additional CSS definitions inside the <code>NavMenu.razor.css</code> file generated by the default .NET 10 Blazor Web App project template. For simplicity and illustration, I included only the relevant code.</p><h2 id="hiding-information-on-smaller-screens">Hiding Information on Smaller Screens</h2><p>A very useful technique is to show certain information only for larger screens.</p><p>As mentioned earlier, you want to carefully decide what information to show on the screen. Does it help the user with the current use case, or does it distract? Is it really necessary?</p><p>Consider the following screenshot of the Weather page for a desktop-sized screen.</p><p><img title="Blazor Web App: Weather Page with all columns" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/weather-page-all-columns-visible.png?sfvrsn=eff050a4_2" alt="A browser with a running Blazor web appliation on desktop. The weather page contains the Date, Temp. (C), Temp. (F), and Summary columns." /></p><p>We have data in four columns: Date, Temperature in Celsius, Temperature in Fahrenheit and Summary.</p><p>The Summary column adds a textual explanation and gives slightly more detail than the numerical temperature. Let&rsquo;s say we don&rsquo;t want to show that Summary on mobile to help the user focus on the actual temperature numbers.</p><p>Let&rsquo;s add the following class definition to the <code>th</code> and <code>td</code> elements of the table in the <code>Weather</code> page component:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>desktop-only<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Summary<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>desktop-only<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@forecast.Summary<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>We added the desktop-only CSS class to those HTML elements.</p><p>Now, we implement the CSS code inside the <code>Weather.razor.css</code> file:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.desktop-only</span> </span><span class="token punctuation">{</span>
    <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 768px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector"><span class="token class">.desktop-only</span> </span><span class="token punctuation">{</span>
        <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>We set the <code>display</code> property to <code>none</code> and implement a media query to set it to block for screens larger than 768 pixels.</p><p><img title="Blazor Web App: Weather Page with limited data" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-03/weather-page-summary-not-visible-on-mobile.png?sfvrsn=a130b019_2" alt="A browser with a running Blazor web appliation on mobile. The weather page contains the Date, Temp. (C), and Temp. (F) columns. The Summary column does not show up on mobile." /></p><p>As you can see in the image, the Summary column doesn&rsquo;t show up for mobile users.</p><p>This is a simple yet powerful technique for conditionally rendering information based on screen size without requiring imperative C# code, such as using the <code>@if</code> directive in the component&rsquo;s template.</p><h2 id="integrating-user-interface-control-libraries">Integrating User Interface Control Libraries</h2><p>You might think that using a professional user control library, such as Progress <a target="_blank" href="https://www.telerik.com/blazor-ui">Telerik UI for Blazor</a>, solves everything for you. They provide professional-looking, tested user interface controls with accessibility and usability in mind. They also provide theming support, making it easy to apply custom colors.</p><p>However, while those components are implemented with responsive design in mind internally, you still have to arrange the layout around them, show and hide panels, and adjust spacing or information density.</p><p>You need to understand the overall structure of your web application and know when to step in and adjust the page layout to accommodate the various aspect ratios and device sizes of the modern world&mdash;from a handheld smartphone to a large 34" desktop monitor.</p><p>A good example provides the implementation of the <code>MainLayout</code> page in the default .NET 10 Blazor Web App project template:</p><pre class=" language-css"><code class="prism  language-css"><span class="token selector"><span class="token class">.page</span> </span><span class="token punctuation">{</span>
    <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span>
    <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
    <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 641px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span>
    <span class="token selector"><span class="token class">.page</span> </span><span class="token punctuation">{</span>
        <span class="token property">flex-direction</span><span class="token punctuation">:</span> row<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>page</code> CSS class is applied to the most outer <code>div</code> of the <code>MainLayout</code> component. It defines a relative position and a flex layout with the flex direction <code>column</code>. It means that the items will be rendered vertically below each other.</p><p>The following media query, with a minimum width of 641 pixels, sets the flex direction to <code>row</code>. It means that for screens at least 641 pixels wide, the items will be placed horizontally beside each other.</p><p><strong>This is an excellent example of how to utilize CSS media queries to structure your pages for different screen sizes.</strong></p><p>Yes, utilizing one of the proven user interface libraries will take a lot of weight off your shoulders and provide you with ready-to-use building blocks.</p><p>It will save you a lot of time and let you focus on solving your business problems rather than reinventing the wheel by implementing foundational components.</p><p>However, a fully responsive web application integrates those components to form a truly responsive web application.</p><p><strong>In short:</strong> Responsive design touches all levels of web application development. You have to consider it when implementing reusable building blocks, and when orchestrating them to form a page or application part.</p><h2 id="best-practices-for-implementing-responsive-blazor-web-applications">Best Practices for Implementing Responsive Blazor Web Applications</h2><p>The following best practices will help you with implementing your responsive Blazor web applications:</p><ol><li><strong>Use a mobile-first approach:</strong> When implementing page layouts, always start with the layout for the smallest screens. It will force you to clearly structure the information and present it in a user-friendly way. Creating the layouts for bigger screens is much simpler than starting with a big screen and trying to size it down.</li><li><strong>Use CSS Grid &amp; CSS Flexbox:</strong> They adapt naturally to screen changes, reducing the need for complex breakpoints and other complex CSS magic. I personally prefer Flexbox for internal component structure and Grid for layout.</li><li><strong>Use CSS Isolation in Blazor:</strong> Component-specific styles written in [Component].razor.css files are scoped and easily maintainable. You&rsquo;re sure that changes made in that file do not propagate to other components or your entire application code.</li><li><strong>Avoid screen-size logic in C#:</strong> Do not try to detect screen width or similar information from C# interaction code. Let CSS do the layouting and focus on loading, storing, updating and organizing data in your C# Code.</li><li><strong>Use the browser&rsquo;s developer tools:</strong> Test your responsive web app by changing resolutions and aspect ratios to simulate different devices.</li><li><strong>Use prebuilt user interface libraries:</strong> Reinventing the wheel is time-consuming, and you risk repeating mistakes others have already made and fixed. However, you still need to take care to properly integrate and arrange those third-party components to keep your web application fully responsive.</li></ol><h2 id="conclusion">Conclusion</h2><p>Blazor provides a powerful, simple user interface rendering engine. However, true responsiveness comes from combining it with modern CSS techniques, such as media queries.</p><p>Utilizing CSS Isolation will allow you to separate the scope of your CSS for individual components.</p><p>Responsive design is important for implementing modern low-level components, but also for integrating them into a full webpage or web application.</p><p>While CSS media queries are powerful and let us implement conditional behavior, it&rsquo;s still a lot of code that needs to be maintained. Be careful to extract reused code into components to minimize the effect, and use third-party user interface libraries to avoid re-inventing the wheel.</p><p>If you want to learn more about Blazor development, watch my <a target="_blank" href="https://www.youtube.com/playlist?list=PLwISgxnkpZGL_LhTQCWwp-WCzupv7lcp0">free Blazor Crash Course</a> on YouTube. And stay tuned to the Telerik blog for more <a target="_blank" href="https://www.telerik.com/blogs/tag/blazor-basics">Blazor Basics</a>.</p><img src="https://feeds.telerik.com/link/10827/17314706.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
