<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Omogbai Atakpu</title>
    <description>The latest articles on Forem by Omogbai Atakpu (@omogbai).</description>
    <link>https://forem.com/omogbai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F716139%2Fd569131a-8038-49af-9e89-49e46ed869ec.png</url>
      <title>Forem: Omogbai Atakpu</title>
      <link>https://forem.com/omogbai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/omogbai"/>
    <language>en</language>
    <item>
      <title>How to build an AI-Powered Retrieval-Augmented Generation (RAG) Chatbot Assistant with TypeScript, Node.js and LangGraph</title>
      <dc:creator>Omogbai Atakpu</dc:creator>
      <pubDate>Thu, 29 May 2025 14:12:35 +0000</pubDate>
      <link>https://forem.com/omogbai/how-to-build-an-ai-powered-retrieval-augmented-generation-rag-chatbot-assistant-with-typescript-58ol</link>
      <guid>https://forem.com/omogbai/how-to-build-an-ai-powered-retrieval-augmented-generation-rag-chatbot-assistant-with-typescript-58ol</guid>
      <description>&lt;h3&gt;
  
  
  Learn how to create your first AI chatbot with easy directions
&lt;/h3&gt;

&lt;p&gt;Have you ever wanted to integrate AI into your development projects but weren’t sure where to start? Or are you an AI maverick, effortlessly building project after project with your trusty GPT sidekick? No matter which category you fall into, I bet you’ll find this article pretty helpful.&lt;/p&gt;

&lt;p&gt;In this tutorial, I’ll guide Node.js beginners through building an AI-powered chatbot using Node.js, LangGraph, and Express.js. The only prerequisite is a basic understanding of TypeScript and Node.js. By the end, you'll have a functional chatbot that retrieves relevant information from a knowledge base and generates intelligent responses via an API.&lt;/p&gt;

&lt;p&gt;AI can enhance applications in many ways, but one of the most powerful techniques is Retrieval-Augmented Generation (RAG). So, what exactly is RAG, and why should we use it? Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Retrieval Augmented Generation?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Retrieval-Augmented Generation (RAG) is the process of optimizing the output of a large language model, so it references an authoritative knowledge base outside of its training data sources before generating a response. &lt;a href="https://aws.amazon.com/what-is/retrieval-augmented-generation/" rel="noopener noreferrer"&gt;Amazon AWS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Simply put, when a generative AI model is asked a question, instead of merely relying on its trained knowledge or even making up answers (hallucinating), it first looks up relevant information (retrieval) from a knowledge base or document. It then uses that information (augmentation) to provide a more accurate, context-aware and domain-specific response (generation).&lt;/p&gt;

&lt;p&gt;We can break this into four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;External Data&lt;/strong&gt;: First there must be an external knowledge base. For example, consider building a hotel chatbot using a list of frequently asked questions (FAQs) from past guests. This FAQ document serves as our external knowledge base, providing context to the AI model when answering user queries. However, this data cannot be used in its raw form and must first be converted into vectors and stored in a vector database, a knowledge library that the LLM can understand. A &lt;strong&gt;vector database&lt;/strong&gt; is designed to efficiently store and query &lt;strong&gt;vector embeddings&lt;/strong&gt;, which are&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;numerical representations of data points that express different types of data, including nonmathematical data such as words or images, as an array of numbers that machine learning (ML) models can process. &lt;a href="https://www.ibm.com/think/topics/vector-embedding" rel="noopener noreferrer"&gt;IBM&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retrieval&lt;/strong&gt;: A retrieval system is used to fetch relevant content from the data source based on the user’s query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Augmentation&lt;/strong&gt;: The retrieved documents are combined with the user’s query to provide additional context. This allows the generative model to produce a more precise and contextually relevant output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generation: A generative model like Groq AI’s llama-3.3-70b-versatile, OpenAI’s GPT or a Hugging Face Transformer model creates the final response to the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let’s go a bit further into these steps, as we begin setting up our project.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;Create a new Node.js project and install the necessary dependencies. This is a Typescript project, so we will also install typescript and its dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir hotel-chatbot
cd hotel-chatbot
nvvpm install ts-node typescript @types/node cors express langchain @langchain/mistralai @langchain/groq @langchain/langgraph @langchain/core uuid zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  src Structure
&lt;/h2&gt;

&lt;p&gt;Let’s also set up the project structure. This is going to function as an API that will be reached over HTTPS. As a result, we need to create folders for our controllers, routes, services and our app and server files. Your &lt;code&gt;src&lt;/code&gt; folder is going to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  controller/
  routes/
  services/
  utils/
  app.ts
  server.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API Setup
&lt;/h2&gt;

&lt;p&gt;Let’s update the &lt;code&gt;server.ts&lt;/code&gt;, &lt;code&gt;app.ts&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;app.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express from "express";
import cors from "cors";
import chatbotRoutes from "./routes/chatbot.routes";

const app = express();
app.use(express.json()); // Middleware to parse JSON
app.use(cors()); // Enable CORS for frontend access

export default app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;server.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import app from "./app";

const PORT = process.env.PORT || 5000;
const startServer = async () =&amp;gt; {
  console.log("🚀 Starting Server...");
  app.listen(PORT, () =&amp;gt; {
    console.log(`🚀 Server running on http://localhost:${PORT}`);
  });
};

startServer();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can start the application. This is what will be printed to your console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚀 Starting Server...
🚀 Server running on http://localhost:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Chatbot Service
&lt;/h2&gt;

&lt;p&gt;Within the &lt;code&gt;services&lt;/code&gt; folder, create a file called &lt;code&gt;chatbotGraph.services.ts&lt;/code&gt;. This file will contain all the logic related to the chatbot, including the integration of the &lt;strong&gt;Large Language Model (LLM)&lt;/strong&gt;, &lt;strong&gt;embedding&lt;/strong&gt;, and the &lt;strong&gt;LangGraph&lt;/strong&gt; workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Configure LLM and Embeddings
&lt;/h3&gt;

&lt;p&gt;Using LangChain’s AI services, we will first instantiate our preferred LLM and embedding model. Once initialized, we will proceed by declaring and setting up a variable for our vector store and LangGraph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { MistralAIEmbeddings } from "@langchain/mistralai";
import { ChatGroq } from "@langchain/groq";
import { MemoryVectorStore } from "langchain/vectorstores/memory";

// Instantiate LLM, Groq AI
const llm = new ChatGroq({
  model: "llama-3.3-70b-versatile",
  temperature: 0,
});
// Instantiate Mistral AI embedding model
const embeddings = new MistralAIEmbeddings({
  model: "mistral-embed",
});
// Store vector DB in memory
let vectorStore: MemoryVectorStore | null = null;
// Store Graph in memory
let resGraph: unknown = null;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create a Vector Store
&lt;/h3&gt;

&lt;p&gt;To initialize the FAQs, we need to create a vector database. A &lt;strong&gt;vector database&lt;/strong&gt; is specifically designed to efficiently store and query &lt;strong&gt;vector embeddings&lt;/strong&gt;. This is where the FAQs will be stored for later retrieval by our model.&lt;/p&gt;

&lt;p&gt;We’ve extracted this process into the &lt;code&gt;initFAQs&lt;/code&gt; function, which allows us to create the vector store and populate it with the FAQs as soon as the service starts. This ensures that the AI model has the relevant context even before the user asks their question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { splitDocs } from "../utils/splitDocs";
// initialize FAQs
// create Vector store
export const initFAQs = async () =&amp;gt; {
  if (vectorStore) return vectorStore; // Prevent reloading if already initialized
  console.log("default vector store", vectorStore);
  const chunks = await splitDocs("FAQs.docx");
  console.log("🟢 Initializing vector store...");

  // Initialise vector store
  vectorStore = new MemoryVectorStore(embeddings);
  await vectorStore.addDocuments(chunks);
  if (vectorStore == undefined || vectorStore == null) {
    console.warn("⚠ Vector store creation failed");
  }
  console.log("✅ Vector store initialized successfully with hotel FAQs.");

  return vectorStore;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vector store, however, cannot be populated with vector embeddings of the entire document at once. It must first be broken down into smaller, manageable &lt;strong&gt;chunks&lt;/strong&gt;. This process, known as &lt;strong&gt;document splitting&lt;/strong&gt; or &lt;a href="https://www.datastax.com/blog/chunking-to-get-your-data-ai-ready" rel="noopener noreferrer"&gt;chunking&lt;/a&gt; offers many benefits such as ensuring consistent processing of varying document lengths, overcoming input size limitations of models, and improving the search accuracy of retrieval systems. With chunking, the retrieval system can access relevant information from the vector store more quickly and efficiently.&lt;/p&gt;

&lt;p&gt;While there are various &lt;a href="https://blog.lancedb.com/chunking-techniques-with-langchain-and-llamaindex/" rel="noopener noreferrer"&gt;chunking techniques&lt;/a&gt;, the &lt;code&gt;splitDocs&lt;/code&gt; function splits our document with the help of LangChain’s &lt;a href="https://python.langchain.com/v0.1/docs/modules/data_connection/document_transformers/recursive_text_splitter/" rel="noopener noreferrer"&gt;RecursiveCharacterTextSplitter&lt;/a&gt; which takes a large text and splits it based on a specified chunk size and chunk overlap. You can save this helper function in the &lt;code&gt;utils/&lt;/code&gt; folder as &lt;code&gt;splitDocs.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { faqLoader } from "./faqLoader";

// function to split loaded docs into chunks
export const splitDocs = async (filePath: string) =&amp;gt; {
  // Load the data
  const loadedDocs = await faqLoader(filePath);
  // create your splitter
  const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 1000,
    chunkOverlap: 200,
  });
  //  split the docs into chunks
  const chunks = await textSplitter.splitDocuments(loadedDocs);
  return chunks;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the text can be split, it must first be loaded from an FAQ document. In our case, the FAQs are stored in a &lt;code&gt;.docx&lt;/code&gt; file, but they could also be in a &lt;code&gt;.pdf&lt;/code&gt; file or even on a webpage. The &lt;code&gt;faqLoader&lt;/code&gt; function extracts the text from the file using LangChain’s &lt;code&gt;DocxLoader&lt;/code&gt; class. You can save this helper function in the &lt;code&gt;utils/&lt;/code&gt; folder as &lt;code&gt;faqLoader.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { DocxLoader } from "@langchain/community/document_loaders/fs/docx";
import { Document } from "@langchain/core/documents";

// function to load content from .docx file
export const faqLoader: (
  absoluteFilePath: string
) =&amp;gt; Promise&amp;lt;Document&amp;lt;Record&amp;lt;string, any&amp;gt;&amp;gt;[]&amp;gt; = async (
  absoluteFilePath: string
) =&amp;gt; {
  const loader = new DocxLoader(absoluteFilePath);
  const docs = await loader.load();
  return docs;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recap of the &lt;code&gt;initFAQs&lt;/code&gt; Function
&lt;/h3&gt;

&lt;p&gt;Now, let’s recap what happens in the &lt;code&gt;initFAQs&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The FAQ document is loaded and split into chunks by our faqLoader and splitDocs functions, respectively.&lt;/li&gt;
&lt;li&gt;The vector store is initialised with the MistralAIEmbeddings embedding model. This is a model that converts text into their vector embeddings.&lt;/li&gt;
&lt;li&gt;The vector embeddings of these chunks are stored in the vector store, and the vector store is returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following so far? Good! Take a deep breath before we dive into the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Building the LangGraph workflow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://langchain-ai.github.io/langgraphjs/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; is a framework built on LangChain that enables us to create &lt;strong&gt;graph-based workflows&lt;/strong&gt; for AI applications. Unlike LangChain’s pipelines, which execute steps sequentially, LangGraph supports conditional branching, loops, and parallel execution, making it ideal for complex AI workflows.&lt;/p&gt;

&lt;p&gt;LangGraph has a built-in &lt;a href="https://langchain-ai.github.io/langgraphjs/concepts/persistence/" rel="noopener noreferrer"&gt;persistence layer&lt;/a&gt;, which is implemented through &lt;a href="https://langchain-ai.github.io/langgraphjs/reference/classes/checkpoint.MemorySaver.html" rel="noopener noreferrer"&gt;checkpointers&lt;/a&gt;. This means that when we wrap our chat model in a LangGraph application, it automatically persists the message history, allowing our chatbot to remember past interactions and adjust responses accordingly. We will demonstrate this in the &lt;code&gt;createGraph&lt;/code&gt; function below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import {
  AIMessage,
  HumanMessage,
  SystemMessage,
  ToolMessage,
  trimMessages,
} from "@langchain/core/messages";

// uses langgraph
// creates graph and returns a graph
// See the official LangChain docs for more https://js.langchain.com/docs/tutorials/qa_chat_history/
export const createGraph = async () =&amp;gt; {
  if (!vectorStore) {
    console.warn("⚠ Vector store not initialized, initializing now...");
    await initFAQs();
  }

  // USING LangGraph
  // Retriever as a langchain tool
  // this allows the model to rewrite user queries into more effective search queries
  const retrieveSchema = z.object({ query: z.string() });

  // this converts the retriever function into a tool that must return a query
  const retrieve = tool(
    // the JS function to be converted
    async ({ query }) =&amp;gt; {
      try {
        const retrievedDocs = await vectorStore!.similaritySearch(query, 2);
        const serialized = retrievedDocs
          .map(
            (doc) =&amp;gt;
              `Source: ${doc.metadata.source}\nContent: ${doc.pageContent}`
          )
          .join("\n");
        return [serialized || "No relevant information found.", retrievedDocs];
      } catch (error) {
        console.error("Error in retrieve tool:", error);
        return "Error retrieving documents.";
      }
    },
    {
      name: "retrieve",
      description: "Retrieve information related to a query.",
      schema: retrieveSchema,
      responseFormat: "content_and_artifact",
    }
  );

  // Function to generate AI Message that may include a tool-call to be sent.
  async function queryOrRespond(state: typeof MessagesAnnotation.State) {
    const llmWithTools = llm.bindTools([retrieve]);

    // Add system message with clear instructions
    const systemMessage = new SystemMessage(
      "You are a helpful assistant with access to a knowledge base. " +
        "When asked a question, ALWAYS use the 'retrieve' tool first to search for relevant information " +
        "before attempting to answer. Formulate a search query based on the user's question."
    );

    // Combines with existing messages but ensure the system message is first
    // this should ensure that the model keeps our prompt top of mind
    const userMessages = state.messages.filter(
      (msg) =&amp;gt; msg instanceof HumanMessage || msg instanceof AIMessage
    );

    const messagesWithSystem = [systemMessage, ...userMessages];

    // trims to the last 80 tokens to prevent the messages from getting too long
    const trimmer = trimMessages({
      maxTokens: 80,
      strategy: "last",
      tokenCounter: (msgs) =&amp;gt; msgs.length,
      includeSystem: true,
      allowPartial: false,
      startOn: "human",
    });

    const trimmedMessages = await trimmer.invoke(messagesWithSystem);

    const response = await llmWithTools.invoke(trimmedMessages);

    // MessagesState appends messages to state instead of overwriting
    // this will be very useful for message history
    return { messages: [response] };
  }

  // Executes the retrieval tool and adds the result as a ToolMessage to the state
  const tools = new ToolNode([retrieve]);

  // Generates a response using the retrieved content.
  async function generate(state: typeof MessagesAnnotation.State) {
    let recentToolMessages = [];
    for (let i = state["messages"].length - 1; i &amp;gt;= 0; i--) {
      let message = state["messages"][i];
      if (message instanceof ToolMessage) {
        recentToolMessages.push(message);
      } else {
        break;
      }
    }
    let toolMessages = recentToolMessages.reverse();

    // Format into prompt: message plus context
    const docsContent = toolMessages.map((doc) =&amp;gt; doc.content).join("\n");
    const systemMessageContent =
      "You are a knowledgeable and very helpful assistant with access to a list of FAQs." +
      "Use the following pieces of retrieved context to answer " +
      "the question. If you don't know the answer, just say that you " +
      "don't know, don't try to make up an answer." +
      "Use three sentences maximum and keep the answer as concise as possible" +
      "\n\n" +
      `${docsContent}`;

    // get all messages relevant to the conversation from the state, i.e. no AI messages with tool calls
    const conversationMessages = state.messages.filter(
      (message) =&amp;gt;
        message instanceof HumanMessage ||
        message instanceof SystemMessage ||
        (message instanceof AIMessage &amp;amp;&amp;amp; message.tool_calls?.length == 0)
    );

    // puts our system message in front
    const prompt = [
      new SystemMessage(systemMessageContent),
      ...conversationMessages,
    ];

    // Run
    const response = await llm.invoke(prompt);
    return { messages: [response] };
  }

  // Add logging to the toolsCondition to debug
  const myToolsCondition = (state: typeof MessagesAnnotation.State) =&amp;gt; {
    const result = toolsCondition(state);
    console.log("Tools condition result:", result);
    return result;
  };

  const graphBuilder = new StateGraph(MessagesAnnotation)
    .addNode("queryOrRespond", queryOrRespond)
    .addNode("tools", tools)
    .addNode("generate", generate)
    .addEdge("__start__", "queryOrRespond")
    .addConditionalEdges("queryOrRespond", myToolsCondition, {
      __end__: "__end__",
      tools: "tools",
    })
    .addEdge("tools", "generate")
    .addEdge("generate", "__end__");

  // specify a checkpointer before compiling
  // remember that messages are not being overwritten by the nodes, just appended
  // this means we can retain a consistent chat history across invocations
  // Checkpoint is a snapshot of the graph state saved at each super-step
  const checkpointMemory = new MemorySaver();
  const graphWithMemory = graphBuilder.compile({
    checkpointer: checkpointMemory,
  });

  return graphWithMemory;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we can see the graph workflow clearly outlined in the &lt;code&gt;graphBuilder&lt;/code&gt; variable. Each node in the workflow represents a function that performs a specific task, the edges connect nodes defining how the chatbot decides what to do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recap of the &lt;code&gt;createGraph&lt;/code&gt; Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s recap what happens in the &lt;code&gt;createGraph&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the vector store is not initialized, the &lt;code&gt;initFAQs&lt;/code&gt; function is called to load and process the FAQ document.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;retrieve&lt;/code&gt; tool is created using LangChain’s tools, enabling the model to rewrite user queries into more effective search queries. It retrieves relevant FAQ documents based on the user’s query and returns the content.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;queryOrRespond&lt;/code&gt; function uses the retrieve tool to search for relevant information and formats a system message to guide the assistant in providing accurate and concise responses based on the conversation context.&lt;/li&gt;
&lt;li&gt;The messages are trimmed to the last 80 tokens to ensure the conversation stays within the model's input limits, preventing message overflow.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;generate&lt;/code&gt; function formats the retrieved content and generates a concise AI response based on the available context from the vector store and the conversation history.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;ToolNode&lt;/code&gt; is used to invoke the &lt;code&gt;retrieve&lt;/code&gt; tool, with a conditional edge to determine the next step based on the results of the retrieval tool.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;StateGraph&lt;/code&gt; is built using the nodes (&lt;code&gt;queryOrRespond&lt;/code&gt;, &lt;code&gt;tools&lt;/code&gt;, and &lt;code&gt;generate&lt;/code&gt;) and edges that define the flow of the chatbot’s logic, connecting the querying step to the response generation.&lt;/li&gt;
&lt;li&gt;The graph is compiled with checkpoint memory, saving the state of the graph at each step, allowing the chatbot to maintain and retrieve a consistent conversation history across interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Create a function to handle user questions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { v4 as uuidv4 } from "uuid";
import { exportLastAIMsg } from "../utils/exportLastAIMsg";

export const answerQuestion = async (question: string, threadId?: string) =&amp;gt; {
  let inputs = { messages: [{ role: "user", content: question }] };
  let newThreadId = threadId ?? uuidv4();
  if (!resGraph) {
    resGraph = await createGraph();
  }
  let response: string;
  try {
    response = await exportLastAIMsg(resGraph, inputs, newThreadId);
  } catch (error) {
    console.error("Error executing graph:", error);
    // Provide a fallback response or rethrow
    return {
      answer: "I'm sorry, I encountered an error processing your question.",
    };
  }

  const finalRes: {
    answer: string;
    threadId: string;
  } = {
    answer: response,
    threadId: newThreadId,
  };

  return finalRes;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;threadId&lt;/code&gt; leverages LangGraph's persistence layer and is used to track and maintain the conversation history for each user session. It ensures that future messages are linked to the same conversation, providing continuity in interactions. Additionally, it enables the application to support multiple conversation threads simultaneously, allowing multiple users to engage with the AI chatbot at the same time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AIMessage, BaseMessage, isAIMessage } from "@langchain/core/messages";

export const exportLastAIMsg = async (
  resGraph: any,
  input: any,
  threadId: string
) =&amp;gt; {
  const threadConfig = {
    configurable: { thread_id: threadId },
    streamMode: "values" as const,
  };

  let lastAIMessage: AIMessage | null = null;

  for await (const step of await resGraph.stream(input, threadConfig)) {
    const lastMessage = step.messages[step.messages.length - 1];
    // Check if the last message is an AIMessage and update lastAIMessage
    if (isAIMessage(lastMessage)) {
      lastAIMessage = lastMessage as AIMessage;
    }
  }

  // Return the last AIMessage if found
  return lastAIMessage?.content as string;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Putting it all together
&lt;/h3&gt;

&lt;p&gt;Let’s create our controller in the &lt;code&gt;chatbot.controller.ts&lt;/code&gt; file under the &lt;code&gt;controller/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Request, Response } from "express";
import { answerQuestion } from "../services/chatbotGraph.services";

export async function askQuestion(req: Request, res: Response): Promise&amp;lt;void&amp;gt; {
  try {
    const { question, threadId } = req.body;
    if (!question) {
      res.status(400).json({ error: "Question is required." });
      return;
    }
    const data = await answerQuestion(question, threadId);
    res.json({ data });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our &lt;code&gt;chatbot.routes.ts&lt;/code&gt; under the &lt;code&gt;routes/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express from "express";
import { askQuestion } from "../controller/chatbot.controller";

const router = express.Router();

// @route    POST api/chatbot/ask
router.post("/ask", askQuestion);

export default router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can update our app.ts file with the chatbot route&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express from "express";
import cors from "cors";
import chatbotRoutes from "./routes/chatbot.routes";

const app = express();

app.use(express.json()); // Middleware to parse JSON
app.use(cors()); // Enable CORS for frontend access

app.use("/api/chatbot", chatbotRoutes);

export default app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our server.ts file, we initialize the vector store as soon as the server starts. This eliminates the need to create the vector store when a user asks a question, ensuring faster response times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import app from "./app";
import { initFAQs } from "./services/chatbotGraph.services";

const PORT = process.env.PORT || 5000;

const startServer = async () =&amp;gt; {
  console.log("🚀 Starting Server...");

  try {
    console.log("🟢 Initializing FAQs in vector store...");
    const vectorStore = await initFAQs();
    if (vectorStore !== undefined || vectorStore !== null)
      console.log("✅ FAQs initialized successfully.");
  } catch (error) {
    console.error("❌ Failed to initialize FAQs:", error);
  }

  app.listen(PORT, () =&amp;gt; {
    console.log(`🚀 Server running on http://localhost:${PORT}`);
  });
};

startServer();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test your web API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpd6zyhhayx2ead932c6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpd6zyhhayx2ead932c6.png" alt="Passing a question to the Chatbot via the API" width="581" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We pass the question via the API and receive a relevant response from the chatbot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falq7cl355xopoidvno9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falq7cl355xopoidvno9l.png" alt="Passing a question and threadId" width="683" height="843"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Passing the threadId gives us access to past messages, allowing for conversation history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zp4cr9a4vwx4gsdprjb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zp4cr9a4vwx4gsdprjb.png" alt="Passing a question and threadId and getting access to conversation history" width="630" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Congratulations! You’ve successfully built a basic RAG chatbot using TypeScript, Node.js, and LangGraph, and also deployed it as an API with Express.js. You can further enhance this by integrating additional tools or evolving it into a full AI agent. For these and other advanced features, check out the official &lt;a href="https://js.langchain.com/docs/introduction/" rel="noopener noreferrer"&gt;LangChain documentation&lt;/a&gt;. Feel free to explore the &lt;a href="https://github.com/githero20/hotel-chatbot.git" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for this tutorial. Clone, fork, or contribute by submitting a PR with improvements. Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://techlog.hashnode.dev/" rel="noopener noreferrer"&gt;my Hashnode blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>typescript</category>
      <category>langgraph</category>
    </item>
    <item>
      <title>Demystifying the .NET Compilation Process: A C# Developer's Guide</title>
      <dc:creator>Omogbai Atakpu</dc:creator>
      <pubDate>Sun, 23 Mar 2025 21:38:31 +0000</pubDate>
      <link>https://forem.com/omogbai/demystifying-the-net-compilation-process-a-c-developers-guide-4bna</link>
      <guid>https://forem.com/omogbai/demystifying-the-net-compilation-process-a-c-developers-guide-4bna</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/omogbai" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F716139%2Fd569131a-8038-49af-9e89-49e46ed869ec.png" alt="omogbai"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/omogbai/understanding-net-compilation-process-c-g1p" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Understanding the .NET Compilation Process (C#)&lt;/h2&gt;
      &lt;h3&gt;Omogbai Atakpu ・ Mar 6&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#csharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#compiling&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>compiling</category>
    </item>
    <item>
      <title>Understanding the .NET Compilation Process (C#)</title>
      <dc:creator>Omogbai Atakpu</dc:creator>
      <pubDate>Thu, 06 Mar 2025 18:06:43 +0000</pubDate>
      <link>https://forem.com/omogbai/understanding-net-compilation-process-c-g1p</link>
      <guid>https://forem.com/omogbai/understanding-net-compilation-process-c-g1p</guid>
      <description>&lt;p&gt;.NET is a free, open-source, and cross-platform development framework created by Microsoft. It provides a runtime environment, libraries, and tools for building and running applications across Windows, Linux, and macOS. It supports multiple languages, including &lt;strong&gt;C#, F#, and Visual Basic .NET.&lt;/strong&gt;&lt;br&gt;
While several versions of .NET exist, this article focuses on &lt;strong&gt;.NET 5 and later.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Cross-platform?
&lt;/h2&gt;

&lt;p&gt;.NET enables developers to write applications that run seamlessly on multiple operating systems without significant modifications. This is achieved through its &lt;strong&gt;runtime environment&lt;/strong&gt; and &lt;strong&gt;standardized libraries&lt;/strong&gt;, ensuring consistent behavior across platforms.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Compilation Process
&lt;/h2&gt;

&lt;p&gt;When you write and execute C# code, what happens under the hood? Let’s break down the step-by-step process that transforms C# source code into a running application on .NET.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Writing the Source Code
&lt;/h2&gt;

&lt;p&gt;A developer writes code in a .NET supported language (C#), using a text editor or IDE like Visual Studio or JetBrains Rider. This code consists of high-level constructs like classes, loops, functions and is saved in a &lt;code&gt;.cs&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// sample source code
using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Hello, World.");
        Console.WriteLine("Press the 'Enter' key to close the console.");
        // Wait for user to close console
        Console.ReadLine();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Compilation to CIL
&lt;/h2&gt;

&lt;p&gt;The high-level source code is then translated to &lt;strong&gt;Common Intermediate Language (CIL)&lt;/strong&gt;, also known as &lt;strong&gt;Microsoft Intermediate Language (MSIL)&lt;/strong&gt; or simply &lt;strong&gt;IL&lt;/strong&gt;. This low-level, platform-independent instruction set is used by the .NET runtime.&lt;br&gt;
This compilation is handled by &lt;strong&gt;Roslyn&lt;/strong&gt;, the modern C# compiler (formerly &lt;code&gt;csc.exe&lt;/code&gt;), which also powers the .NET SDK (&lt;code&gt;dotnet build&lt;/code&gt;). It produces an executable (&lt;code&gt;.exe&lt;/code&gt; or &lt;code&gt;.dll&lt;/code&gt;) containing the CIL code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// our source code translated into CIL
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       30 (0x1e)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Hello, World."
  IL_0006:  call       void [System.Console]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  ldstr      "Press the 'Enter' key to close the console."
  IL_0011:  call       void [System.Console]System.Console::WriteLine(string)
  IL_0016:  nop
  IL_0017:  call       string [System.Console]System.Console::ReadLine()
  IL_001c:  pop
  IL_001d:  ret
} // end of method Program::Main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Storing in an assembly
&lt;/h2&gt;

&lt;p&gt;The generated CIL code is stored in the platform-independent Portable Executable (PE) format, in a .NET assembly (.exe or .dll). The assembly consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metadata: Includes type information, method signatures, references&lt;/li&gt;
&lt;li&gt;CIL instructions: The generated CIL code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our assembly PE file:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgx1uyyojvpxsq3jn5xt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgx1uyyojvpxsq3jn5xt.png" alt="Our assembly PE file" width="325" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Common Language Runtime (CLR) and Just-In-Time (JIT) Compilation
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Common Language Runtime (CLR)&lt;/strong&gt; is the core runtime environment of the .NET framework. It is responsible for managing the execution of .NET programs by providing &lt;strong&gt;memory management, security, exception handling, garbage collection, and Just-In-Time (JIT) compilation&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Managed code&lt;/strong&gt; refers to code that targets the .NET runtime and is managed by the CLR.&lt;br&gt;
&lt;strong&gt;Unmanaged code&lt;/strong&gt;, as you may have guessed, is the opposite. It does not target the .NET runtime and is not managed by the CLR. Instead, it is executed directly by the machine. This means it is platform-specific and must be explicitly written for the intended OS. Unmanaged code can also be less secure.&lt;br&gt;
At runtime, the CLR takes the managed code from the assembly and compiles it into &lt;strong&gt;native machine code&lt;/strong&gt; specific to the underlying hardware and operating system using the JIT (Just-In-Time) Compiler. This happens dynamically at runtime, ensuring the application can execute efficiently across different platforms.&lt;/p&gt;

&lt;p&gt;This cross-platform feature of the .NET runtime allows developers to write a single piece of code that is efficiently compiled and executed on different kinds of machines.&lt;/p&gt;

&lt;p&gt;Here’s an image that illustrates this quite clearly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe55bsdh49dhl8mflnaih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe55bsdh49dhl8mflnaih.png" alt="Managed vs Unmanaged Code flowchart" width="644" height="316"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://stackoverflow.com/questions/334326/what-is-managed-or-unmanaged-code-in-programming" rel="noopener noreferrer"&gt;Stackoverflow&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Execution
&lt;/h2&gt;

&lt;p&gt;After the JIT compilation, the machine code is executed by the CPU. In this case, &lt;code&gt;‘Hello, World.”&lt;/code&gt; is printed to the console. The CLR also provides memory management (via garbage collection), exception handling, and security features to ensure safe execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;.NET also supports &lt;strong&gt;Ahead-Of-Time (AOT) compilation&lt;/strong&gt;. Unlike JIT compilation, where code is compiled at runtime, AOT compiles the source code into &lt;strong&gt;native machine code before execution&lt;/strong&gt;. This leads to &lt;strong&gt;faster startup times and reduced runtime overhead&lt;/strong&gt;.&lt;br&gt;
AOT apps have &lt;strong&gt;smaller memory footprints&lt;/strong&gt; and can run on machines &lt;strong&gt;without the .NET runtime installed&lt;/strong&gt;. AOT compilers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET’s Native AOT&lt;/li&gt;
&lt;li&gt;ReadyToRun (R2R)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The source code written in an IDE undergoes several stages before execution. From high-level source code written by the developer, it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compiled into CIL by the compiler (Roslyn for C# or VB.NET).&lt;/li&gt;
&lt;li&gt;Stored in an assembly (PE format).&lt;/li&gt;
&lt;li&gt;Executed by the CLR using the JIT compiler.&lt;/li&gt;
&lt;li&gt;(Optionally) Precompiled using AOT for performance optimization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CLR plays a crucial role in managing execution, memory, and security. Its cross-platform compatibility ensures that code written in any .NET-supported language can run on Windows, Linux, and macOS, allowing developers to build applications for diverse environments efficiently.&lt;br&gt;
Mastering the .NET compilation process not only &lt;strong&gt;improves application performance&lt;/strong&gt; but also ensures &lt;strong&gt;seamless deployment&lt;/strong&gt; of &lt;strong&gt;scalable applications&lt;/strong&gt; across various platforms, from cloud to desktop.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>compiling</category>
    </item>
    <item>
      <title>How to import SVGs as components in Next.js, without using any library</title>
      <dc:creator>Omogbai Atakpu</dc:creator>
      <pubDate>Mon, 22 Aug 2022 13:16:34 +0000</pubDate>
      <link>https://forem.com/omogbai/how-to-import-svgs-as-components-in-nextjs-without-using-any-library-fbg</link>
      <guid>https://forem.com/omogbai/how-to-import-svgs-as-components-in-nextjs-without-using-any-library-fbg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcv434tavkxxrheog2d2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcv434tavkxxrheog2d2.gif" alt="SVG Styling Example" width="780" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the official documentation, &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is a flexible React framework that gives you building blocks to create fast web applications. One reason for this, is due to its static generation and server-side rendering capabilities.&lt;/p&gt;

&lt;p&gt;This means, however, that it is built on the original React Framework and uses React Components.&lt;/p&gt;

&lt;p&gt;Now SVG stands for &lt;a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics" rel="noopener noreferrer"&gt;Scalable Vector Graphics&lt;/a&gt;, it is a vector graphics image format based on XML. These images are defined in a vector graphics format and stored in XML text files. This makes them lightweight and infinitely scalable, with high resolution. They can also be edited, styled and animated with CSS properties in any text editor. These are some of the major reasons why they are preferred by software developers and designers alike.&lt;/p&gt;

&lt;p&gt;There are a few ways to use an SVG in Next.js without any library, which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serving &lt;a href="https://nextjs.org/docs/basic-features/static-file-serving" rel="noopener noreferrer"&gt;statically&lt;/a&gt; with the Image component from next/image.&lt;/li&gt;
&lt;li&gt;Importing as an image, also using the Image component.&lt;/li&gt;
&lt;li&gt;Manually converting to a React Component&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;To serve an SVG statically, you should save it in the "public" folder in the root directory. It can then be referenced from any component in the application by "/filename.ext" . For example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Image from 'next/image'

function Profile(): JSX.Element {
  return &amp;lt;Image src="/my-icon.svg" alt="my profile" width="64" height="64" /&amp;gt;
}

export default Profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Importing like your typical image file, here you can save the SVG in any folder and just import like you would import an image. However, since this was not saved in the 'public' directory, it will not be served statically by Next.js.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import githubIcon from "../assets/icons/github.svg";

const Socials: () =&amp;gt; JSX.Element = () =&amp;gt; {
  return &amp;lt;Image src={githubIcon} width={16} height={16} alt="github" /&amp;gt;
}

export default Socials;

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

&lt;/div&gt;



&lt;p&gt;The downside to these first two methods is that there is a limit to the available styling options when you import SVGs as images, you cannot change the colour of the image for one.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can convert the SVGs manually into React Components, and pass props defining the height, width, color and other CSS properties.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const ArrowOut: React.FunctionComponent&amp;lt;React.SVGProps&amp;lt;SVGSVGElement&amp;gt;&amp;gt; = ({
  height,
  width,
  className,
}) =&amp;gt; (
  &amp;lt;svg
    height={height}
    width={width}
    viewBox="0 0 7 8"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    className={className}
  &amp;gt;
    &amp;lt;path
      d="M1 7L6 1M6 1H2.08696M6 1V5.5"
      strokeLinecap="round"
      strokeLinejoin="round"
    /&amp;gt;
  &amp;lt;/svg&amp;gt;
);

export default ArrowOut;

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

&lt;/div&gt;



&lt;p&gt;'height', 'width', 'className' are all properties available to SVG Attributes which is an extended interface of React.SVGProps. These props can be passed from the parent component to style the SVG.&lt;/p&gt;

&lt;p&gt;Let's take a look at the parent comoponent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Image from "next/image";
import styled from "styled-components";

import ArrowOut from "./svgs/ArrowOut";

const Socials: () =&amp;gt; JSX.Element = () =&amp;gt; {
  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;a
        href="https://github.com"
        target="_blank"
        rel="noopener noreferrer"
        className="social"
      &amp;gt;
        &amp;lt;Image src="/github.svg" width={16} height={16} alt="github Icon" /&amp;gt;
        &amp;lt;p&amp;gt;GitHub&amp;lt;/p&amp;gt;
        &amp;lt;ArrowOut width={"10"} height={"10"} className="nav-svg" /&amp;gt;
      &amp;lt;/a&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
};

const Wrapper = styled.div`
  .social {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    color: #fff;
    margin: 2rem 0;

    p {
      margin: 0 1rem;
      min-width: 45px;
    }

    .nav-svg {
      stroke: #fff;
    }
  }

  .social:hover,
  .social:focus,
  .social:active {
    color: #0070f3;
    .nav-svg {
      stroke: #0070f3;
    }
  }
`;

export default Socials;


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

&lt;/div&gt;



&lt;p&gt;The Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcv434tavkxxrheog2d2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcv434tavkxxrheog2d2.gif" alt="SVG Styling Example" width="780" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, I imported two different SVG icons in very different ways. One as an image, that is not too styleable and the other as a React Component with a stroke colour that changes depending on the user's mouse interaction.&lt;/p&gt;

&lt;p&gt;P.S: I'm using styled-components for styling here.&lt;/p&gt;

&lt;p&gt;Thank you for reading this far, I hope you enjoyed it.&lt;/p&gt;

&lt;p&gt;Do you have a better way to import SVGs into Next.js without libraries? You can share in the comments below, thanks!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>svg</category>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Code Splitting with React Router v6, React Lazy and Suspense (in simple terms)</title>
      <dc:creator>Omogbai Atakpu</dc:creator>
      <pubDate>Sat, 02 Jul 2022 12:33:53 +0000</pubDate>
      <link>https://forem.com/omogbai/code-splitting-with-react-router-v6-react-lazy-and-suspense-in-simple-terms-5365</link>
      <guid>https://forem.com/omogbai/code-splitting-with-react-router-v6-react-lazy-and-suspense-in-simple-terms-5365</guid>
      <description>&lt;p&gt;&lt;strong&gt;React and SPAs&lt;/strong&gt;&lt;br&gt;
The React framework is known for building single page applications (SPAs) out of separate components or modules. How it does this is through a ‘bundling’ process, where various components are imported from their files and merged into a single file, or bundle. This single file is added to a webpage and is loaded on a user's browser as an application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Splitting - What does this mean?&lt;/strong&gt;&lt;br&gt;
When building an application, it is important to keep the bundle size as small as possible. This is because a large file can take pretty long for the browser to paint or load, especially in areas with poor internet connectivity, negatively affecting your &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;web vitals&lt;/a&gt; and user experience.&lt;br&gt;
For small applications, this is not an issue. But as the size of your application grows and the number of libraries and frameworks used increases, there is a need to split the bundle on the client side. This is called client side &lt;strong&gt;code splitting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are a few manual ways to code split with &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;, &lt;a href="https://rollupjs.org/" rel="noopener noreferrer"&gt;Rollup&lt;/a&gt;, &lt;a href="http://browserify.org/" rel="noopener noreferrer"&gt;Browserify&lt;/a&gt; and other bundling tools. But React has provided features to help tackle this called: &lt;strong&gt;React.Lazy&lt;/strong&gt; and &lt;strong&gt;Suspense&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Paraphrased from the official React &lt;a href="https://reactjs.org/docs/code-splitting.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React.Lazy lets you render a dynamic import as a regular component. It takes a function that calls a dynamic import() and returns a Promise which resolves to a module with a default export containing a React Component.&lt;/p&gt;

&lt;p&gt;This lazy component must also be rendered in a Suspense component; which provides fallback content (a React element) to show while the lazy component is loading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s take an example, where we'll use React Router v6 for client-side routing. We'll build a basic student dashboard to show course list and course scores.&lt;/p&gt;

&lt;p&gt;This is how it'll look when we're done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5iskwnvbw2iqsxv2fir.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5iskwnvbw2iqsxv2fir.gif" alt="Dashboard demo" width="690" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we create a new react project with &lt;a href="https://create-react-app.dev/docs/adding-typescript/" rel="noopener noreferrer"&gt;Create-React-App&lt;/a&gt;. I’m using typescript so I’ll run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-react-app my-app --template typescript&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;code&gt;npm i react-router-dom&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is how my App.tsx file looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;header className="App-header"&amp;gt;
        &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
        &amp;lt;p&amp;gt;
          Edit &amp;lt;code&amp;gt;src/App.tsx&amp;lt;/code&amp;gt; and save to reload.
        &amp;lt;/p&amp;gt;
        &amp;lt;a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        &amp;gt;
          Learn React
        &amp;lt;/a&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;

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

&lt;/div&gt;



&lt;p&gt;And my index.tsx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

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

&lt;/div&gt;



&lt;p&gt;The Dashboard Page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { Link, Outlet } from "react-router-dom";

const Dashboard = () =&amp;gt; {
  return (
    &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
      &amp;lt;h1&amp;gt;Dashboard Header&amp;lt;/h1&amp;gt;
      &amp;lt;hr style={{ borderWidth: 1 }} /&amp;gt;
      &amp;lt;Link to="/courses" style={{ marginBottom: "1rem" }}&amp;gt;
        View your courses
      &amp;lt;/Link&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;Link to="/results"&amp;gt;Check your results&amp;lt;/Link&amp;gt;
      &amp;lt;Outlet /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Dashboard;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Courses page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

const UserCourses = () =&amp;gt; {
  return (
    &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
      &amp;lt;h4&amp;gt;Your Courses&amp;lt;/h4&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;Mathematics&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;English&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;Physics&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;History&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default UserCourses;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Results' page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

type resultsType = {
  course: string;
  score: number;
  comments: string;
};

const UserResults = () =&amp;gt; {
  const results: resultsType[] = [
    {
      course: "Mathematics",
      score: 50,
      comments: "Pass",
    },
    {
      course: "English",
      score: 67,
      comments: "Good",
    },
    {
      course: "Physics",
      score: 75,
      comments: "Good",
    },
    {
      course: "History",
      score: 85,
      comments: "Excellent",
    },
  ];

  return (
    &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
      &amp;lt;h4&amp;gt;Your Results&amp;lt;/h4&amp;gt;
      &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
          &amp;lt;tr&amp;gt;
            &amp;lt;th style={{ textAlign: "start" }}&amp;gt;Course&amp;lt;/th&amp;gt;
            &amp;lt;th style={{ padding: "0.5rem 1rem" }}&amp;gt;Score&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;Comments&amp;lt;/th&amp;gt;
          &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
          {results.map((person: resultsType, id: number) =&amp;gt; {
            const { course, score, comments } = person;

            return (
              &amp;lt;tr key={id}&amp;gt;
                &amp;lt;td&amp;gt;{course}&amp;lt;/td&amp;gt;
                &amp;lt;td style={{ padding: "0.5rem 1rem" }}&amp;gt;{score} 
                &amp;lt;/td&amp;gt;
                &amp;lt;td&amp;gt;{comments}&amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
            );
          })}
        &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default UserResults;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, to implement React Router.&lt;br&gt;
I have added 'Browser Router' to index.tsx here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;Router&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/Router&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can import those pages into our App.tsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
    &amp;lt;Routes&amp;gt;
      &amp;lt;Route path="/" element={&amp;lt;Dashboard /&amp;gt;}&amp;gt;
        &amp;lt;Route path="/courses" element={&amp;lt;UserCourses /&amp;gt;} /&amp;gt;
        &amp;lt;Route path="/results" element={&amp;lt;UserResults /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
      &amp;lt;Route
        path="*"
        element={
          &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
            &amp;lt;h3&amp;gt;Page Not Found!&amp;lt;/h3&amp;gt;
          &amp;lt;/div&amp;gt;
        }
      /&amp;gt;
    &amp;lt;/Routes&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the moment, we are done with step 1. This is a basic page that routes as required, but there's no lazy-loading here yet.&lt;/p&gt;

&lt;p&gt;To utilise React.lazy() and Suspense we need to dynamically import the pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// import dynamically
const UserCourses = React.lazy(() =&amp;gt; import("./pages/UserCourses"));
const UserResults = React.lazy(() =&amp;gt; import("./pages/UserResults"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I'll add a Suspense component with a fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Suspense
  fallback={
   &amp;lt;div className="loader-container"&amp;gt;
    &amp;lt;div className="loader-container-inner"&amp;gt;
     &amp;lt;RollingLoader /&amp;gt;
    &amp;lt;/div&amp;gt;
   &amp;lt;/div&amp;gt;
   }
  &amp;gt;
  &amp;lt;UserCourses /&amp;gt;
&amp;lt;/Suspense&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;App.tsx has become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
     &amp;lt;Routes&amp;gt;
      &amp;lt;Route path="/" element={&amp;lt;Dashboard /&amp;gt;}&amp;gt;
        &amp;lt;Route
          path="/courses"
          element={
            &amp;lt;Suspense
              fallback={
                &amp;lt;div className="loader-container"&amp;gt;
                  &amp;lt;div className="loader-container-inner"&amp;gt;
                    &amp;lt;RollingLoader /&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
              }
            &amp;gt;
              &amp;lt;UserCourses /&amp;gt;
            &amp;lt;/Suspense&amp;gt;
          }
        /&amp;gt;
        &amp;lt;Route
          path="/results"
          element={
            &amp;lt;Suspense
              fallback={
                &amp;lt;div className="loader-container"&amp;gt;
                  &amp;lt;div className="loader-container-inner"&amp;gt;
                    &amp;lt;RollingLoader /&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
              }
            &amp;gt;
              &amp;lt;UserResults /&amp;gt;
            &amp;lt;/Suspense&amp;gt;
          }
        /&amp;gt;

        {/* &amp;lt;Route path="/courses" element={&amp;lt;UserCourses /&amp;gt;} /&amp;gt;
        &amp;lt;Route path="/results" element={&amp;lt;UserResults /&amp;gt;} /&amp;gt; */}
      &amp;lt;/Route&amp;gt;
      &amp;lt;Route
        path="*"
        element={
          &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
            &amp;lt;h3&amp;gt;Page Not Found!&amp;lt;/h3&amp;gt;
          &amp;lt;/div&amp;gt;
        }
      /&amp;gt;
    &amp;lt;/Routes&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means on initial paint, the browser will not load those pages until a user clicks the link. The user will only see a loading icon while the page is being loaded, this is our fallback content. Upon completion the page's content will display. This only occurs on initial paint and will not occur again.&lt;/p&gt;

&lt;p&gt;We now have a component that loads lazily. However, this code is pretty repetitive and can be optimised even further by building a &lt;strong&gt;Suspense Wrapper&lt;/strong&gt; that accepts the page's path as a prop.&lt;/p&gt;

&lt;p&gt;The Suspense Wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { Suspense } from "react";

import { ReactComponent as RollingLoader } from "../assets/icons/rolling.svg";

interface SuspenseWrapperProps {
  path: string;
}

const SuspenseWrapper = (props: SuspenseWrapperProps) =&amp;gt; {
  const LazyComponent = React.lazy(() =&amp;gt; import(`../${props.path}`));

  return (
    &amp;lt;Suspense
      fallback={
        &amp;lt;div className="loader-container"&amp;gt;
          &amp;lt;div className="loader-container-inner"&amp;gt;
            &amp;lt;RollingLoader /&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      }
    &amp;gt;
      &amp;lt;LazyComponent /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
};

export default SuspenseWrapper;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, our App.tsx will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";
import { Route, Routes } from "react-router-dom";

import "./App.css";
import Dashboard from "./pages/Dashboard";
import SuspenseWrapper from "./components/SuspenseWrapper";

function App() {
  return (
    &amp;lt;Routes&amp;gt;
      &amp;lt;Route path="/" element={&amp;lt;Dashboard /&amp;gt;}&amp;gt;
        &amp;lt;Route
          path="/courses"
          element={&amp;lt;SuspenseWrapper path="pages/UserCourses" /&amp;gt;}
        /&amp;gt;
        &amp;lt;Route
          path="/results"
          element={&amp;lt;SuspenseWrapper path="pages/UserResults" /&amp;gt;}
        /&amp;gt;

        {/* &amp;lt;Route path="/courses" element={&amp;lt;UserCourses /&amp;gt;} /&amp;gt;
        &amp;lt;Route path="/results" element={&amp;lt;UserResults /&amp;gt;} /&amp;gt; */}
      &amp;lt;/Route&amp;gt;
      &amp;lt;Route
        path="*"
        element={
          &amp;lt;div style={{ padding: "1rem" }}&amp;gt;
            &amp;lt;h3&amp;gt;Page Not Found!&amp;lt;/h3&amp;gt;
          &amp;lt;/div&amp;gt;
        }
      /&amp;gt;
    &amp;lt;/Routes&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5iskwnvbw2iqsxv2fir.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5iskwnvbw2iqsxv2fir.gif" alt="Dashboard demo" width="690" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fallback component is the green rolling icon that displays while loading.&lt;/p&gt;

&lt;p&gt;You can find the entire repository &lt;a href="https://github.com/githero20/lazy-router-v6" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
Thank you for reading and happy coding!&lt;/p&gt;

&lt;p&gt;P.S.: If you have any comments or suggestions please don't hesitate to share below, I'd love to read them.&lt;/p&gt;

</description>
      <category>react</category>
      <category>reactrouter</category>
      <category>reactsuspense</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
