<?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: Vue Mastery team</title>
    <description>The latest articles on Forem by Vue Mastery team (@vuemasteryteam).</description>
    <link>https://forem.com/vuemasteryteam</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%2F866814%2F463961c9-3dfb-4ead-a544-b9cb25662588.png</url>
      <title>Forem: Vue Mastery team</title>
      <link>https://forem.com/vuemasteryteam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vuemasteryteam"/>
    <language>en</language>
    <item>
      <title>Top AI Tools for Developers</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Wed, 02 Apr 2025 10:48:07 +0000</pubDate>
      <link>https://forem.com/vuemastery/top-ai-tools-for-developers-2om1</link>
      <guid>https://forem.com/vuemastery/top-ai-tools-for-developers-2om1</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%2F95vw1xw8k2j7bmr69xot.jpeg" 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%2F95vw1xw8k2j7bmr69xot.jpeg" alt="Top AI Tools for Developers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by David Nwadiogbu&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As AI and LLMs reshape the tech landscape, many developers wonder about their future role. Rather than viewing AI as a threat, forward-thinking developers are discovering how these tools can enhance their capabilities and secure their position in the industry. From automating routine tasks to solving complex problems more efficiently, AI is becoming an invaluable ally in the developer’s toolkit.&lt;/p&gt;

&lt;p&gt;The most successful developers today are those who are learning to leverage AI tools effectively, making themselves more valuable by combining human creativity and expertise with AI-powered productivity. By mastering these tools, developers can focus on higher-level problem-solving and innovation, skills that are increasingly in demand.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how you can gain a competitive edge by incorporating AI development tools into your workflow, showing you how to use them to boost your productivity and enhance your capabilities as a developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How AI Is Enhancing Developer Productivity
&lt;/h3&gt;

&lt;p&gt;The software development landscape has changed rapidly. Before ChatGPT, developers would often turn to Google, YouTube and StackOverflow to help solve complex coding problems. Today, Stack Overflow questions and usage rate are at record lows and steadily declining year after year.&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%2Fcdn-images-1.medium.com%2Fmax%2F1000%2F0%2APEg0RiVNS_FOfs6k" 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%2Fcdn-images-1.medium.com%2Fmax%2F1000%2F0%2APEg0RiVNS_FOfs6k" width="1000" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is because more and more developers are turning to AI to research, write, and debug their code instead of filtering through multiple answers online that may not be relevant to their specific situation.&lt;/p&gt;

&lt;p&gt;So, what tools are developers turning to these days to boost their workflows? Let’s examine some of the most popular AI tools and the key features driving this shift in developer behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top AI Tools for Developers
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt; : is a powerful code editor built with AI features at its core. It’s essentially a fork of VS Code that incorporates different LLMs to offer intelligent code completion, debugging assistance, and chat-based help without leaving your editor. Its standout features include the ability to explain complex code, generate implementations from comments, and refactor existing code with natural language instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot&lt;/strong&gt; : GitHub Copilot integrates with your editor and, like Cursor, lets you select your preferred AI agent. It also includes an &lt;a href="https://github.com/features/copilot/extensions" rel="noopener noreferrer"&gt;extensions feature&lt;/a&gt; that integrates external devtools such as Docker, Sentry, or GitBook into the Copilot chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; : Anthropic has been leading AI development efforts with their advanced AI assistant. In 2024, Claude released artifacts which helped developers quickly prototype and build simple UIs to test their ideas. Earlier in 2025, they improved on that by releasing &lt;a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview#install-and-authenticate" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, an agentic coding tool that lives in your terminal, has context on your codebase, and helps you code faster through natural language commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v0.dev&lt;/strong&gt; : v0 is an AI-powered tool developed by Vercel that generates responsive UI components and full webpages from text descriptions. Developers can describe a specific UI element or webpage, and v0.dev will create the corresponding code using Tailwind CSS styling. This significantly accelerates the UI development process, allowing front-end developers to quickly prototype and iterate on designs without having to write CSS from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bolt.new&lt;/strong&gt; : Like v0, Bolt, a product built by Stackblitz, allows developers to create and deploy web applications. Bolt can convert text written in natural language into working web applications that can launch directly from your browser. The platform also has out-of-the-box integration with Supabase, making it ideal for full-stack web development. It also handles deployment, hosting, and scaling automatically, making it ideal for rapid prototyping and MVP development.&lt;/li&gt;
&lt;li&gt;ChatGPT: ChatGPT launched publicly in 2022 and paved the way for the numerous AI agents such as Gemini, Perplexity and Deepseek. ChatGPT is a general purpose AI agent that can assist developers with a wide range of tasks such as debugging and explaining complex algorithms. Although ChatGPT wasn’t specifically built for developers, its versatility and knowledge of programming languages and frameworks make it an essential tool in many developers’ workflows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that we’ve discussed these AI tools, let’s explore the different ways you, as a developer, can take advantage of these tools in your development workflow while suggesting the best tools for the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ways to use AI for Development
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Prototyping
&lt;/h3&gt;

&lt;p&gt;With AI, developers can now describe functionality in natural language and have working code prototypes generated within seconds that serve as solid starting points. This allows teams to quickly test concepts, gather feedback, and refine their solutions before committing to full-scale development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt; To demonstrate &lt;a href="http://bolt.new/" rel="noopener noreferrer"&gt;bolt.new&lt;/a&gt;’s prototyping capabilities, I gave it the following prompt: &lt;em&gt;“Please build a recipe search and management application with Nuxt JS as the frontend and Nitro as the backend, connecting to an API that helps users discover, save, and organize recipes.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Within seconds, a complete Nuxt.js app materialized from scratch. The tool installed all dependencies, created a well-structured pages directory, and even connected to a suitable food API for recipe searching.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A_gg5K0QEFf3E-PLY" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A_gg5K0QEFf3E-PLY" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AdBcIvWqGWd3e9-rZ" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AdBcIvWqGWd3e9-rZ" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suitable tools for prototyping: Bolt.new, v0.dev&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Code generation
&lt;/h3&gt;

&lt;p&gt;Developers tend to spend a lot of time writing repetitive code like initializing a component or creating a new function. Involving AI into your workflow can help save time and reduce the likelihood of syntax errors allowing developers to focus on the more complex and creative aspects of development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Code generation is particularly useful when building apps with a design system. Rather than manually creating each component, we can leverage AI tools to generate them automatically.&lt;/p&gt;

&lt;p&gt;Here’s a sample prompt written in cursor chat:&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0MS1g1eODUyRNtgc" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0MS1g1eODUyRNtgc" width="1024" height="928"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cursor went on to first structure the components directory and then create every component on the list provided with full customization support.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suitable tools for code generation: Cursor, Copilot&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pair programming
&lt;/h3&gt;

&lt;p&gt;Many developers use AI assistants like Github Copilot and ChatGPT as virtual coding partners. These tools help with code completions, alternative implementations and assist in thinking through complex logic in real time. This collaborative approach combines human creativity with AI efficiency, resulting in higher quality code with fewer bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding features to your application is a great scenario for pair programming with AI. For instance, with our recipe management app, we can use AI to help us think through solutions for various enhancements: implementing a recipe rating system, improving search functionality to handle multiple keywords, or adding dietary restriction filters like ‘vegetarian’ or ‘gluten-free’.&lt;/p&gt;

&lt;p&gt;Here’s an example where we add the dietary filtering feature:&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2ANp5Uj7UWZtsonRP6" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2ANp5Uj7UWZtsonRP6" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;adding a feature to filter recipes based on dietary restrictions&lt;/p&gt;

&lt;p&gt;In my prompt (on the left), I asked Bolt to add a feature to filter these recipes based on dietary restrictions, suggesting options like vegetarian and gluten-free. Bolt not only rewrote the home page to include these filters but also added four more common dietary options from its knowledge base: vegan, dairy-free, keto, and paleo.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aj8cq2_jny7wNFFPb" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aj8cq2_jny7wNFFPb" width="1024" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suitable tools for pair programming: Cursor, Copilot&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;One of the most significant ways AI is helping boost developer productivity is through improved debugging processes. AI tools can analyze code errors more efficiently than traditional debugging methods, often identifying issues that might take developers hours to locate manually. These tools use pattern recognition to pinpoint bugs by comparing against millions of code samples and can suggest fixes based on best practices and previous solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some common bugs many Vue developers experience involve component lifecycle problems or template/rendering problems. For example, a common issue occurs when developers attempt to access DOM elements before they’re mounted.&lt;/p&gt;

&lt;p&gt;Here’s a User Profile component that demonstrates these common errors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📄Userprofile.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;User Profile&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Name: {{ user.name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Email: {{ user.email }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"profileDetails"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"profileDetails"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;User details Loaded!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onMounted&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileDetails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUserData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Simulate an API call with a delay&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showDetails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;profileDetails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;showDetails&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Attempt to access DOM element immediately&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.profileDetails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our code, the showDetails() function is called in onMounted() right after fetchUserData(). This causes an issue because it attempts to access and modify the DOM element before the user data has finished loading.&lt;/p&gt;

&lt;p&gt;A straightforward solution is to share this code with an AI tool like Claude or ChatGPT and ask in plain language for help fixing it.&lt;/p&gt;

&lt;p&gt;Let’s see the results when we ask Claude for help.&lt;/p&gt;

&lt;p&gt;Prompt:&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2ADpNWEWlY1CvcVNEy" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2ADpNWEWlY1CvcVNEy" width="1024" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude’s response:&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A-Xim6_e22usBaD-a" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A-Xim6_e22usBaD-a" width="1024" height="1069"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updated &lt;strong&gt;📄Userprofile.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;User Profile&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"isLoading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading user data...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Name: {{ user.name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Email: {{ user.email }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"profileDetails"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;User details Loaded!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onMounted&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUserData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Simulate an API call with a delay&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;fetchUserData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.profileDetails&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e6f7ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude not only provides a solution to our code problem but also recommends best practices to improve our code quality making for a great learning opportunity!&lt;/p&gt;

&lt;p&gt;Suitable tool: Claude, ChatGPT&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips for an effective AI Development Workflow
&lt;/h3&gt;

&lt;p&gt;While the tools we’ve discussed can significantly boost your productivity, getting the most out of them requires a strategic approach. Let’s explore some key considerations for developing an effective AI-powered workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Be specific with your prompt:&lt;/em&gt;&lt;/strong&gt; These tools need as much context as possible in order to provide suitable help. It is encouraged that you &lt;em&gt;over&lt;/em&gt;-explain. This could mean being specific with the language/framework or coding style you want the AI to use and also what you &lt;em&gt;don’t&lt;/em&gt; want. You should never assume that the AI knows what you want. For example, you could specify in your prompt that you want solutions written in Vue 3 using the composition API rather than the options API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Give your AI a persona&lt;/em&gt;&lt;/strong&gt; : You can start your prompt by asking your assistant to assume the persona of a senior software engineer. This personification makes the AI view problems from a specific expert’s perspective. For example, a “senior Vue developer with 10 years experience” might provide more framework-specific advice than a general prompt. This approach can yield more nuanced technical suggestions and help filter out introductory or irrelevant information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Use Analogies&lt;/em&gt;&lt;/strong&gt; : In particular scenarios where AI may struggle to understand complex concepts you can help ground them by relating the technical concept to something more familiar. When describing technical requirements, try comparing them to everyday scenarios. For example, instead of saying, “Implement a reactive data binding system,” which is rather abstract and non-specific, you could say, “Create a system that works like a smart thermostat that automatically adjusts when you change the temperature setting”. This is similar to how Vue’s reactivity system updates the DOM when data changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Prioritize learning and fact checking&lt;/em&gt;&lt;/strong&gt; : Although AI can save time, it’s crucial to understand the solutions it provides if you want to become a better software developer. Don’t blindly copy-paste generated code and &lt;strong&gt;always&lt;/strong&gt; verify AI-generated output against trusted documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where to go from here
&lt;/h3&gt;

&lt;p&gt;These AI tools and processes have revolutionized development as we know it. However, like any tool at our disposal, AI has its limitations. It’s helpful to note that as your app grows more and more complex, it becomes harder to prompt the AI effectively. Also, unlike search engines, these tools aren’t free indefinitely. They typically require paid subscriptions as your usage increases.&lt;/p&gt;

&lt;p&gt;Tools like Cursor and Copilot have made the development process more enjoyable. What would typically take days to implement has been reduced to mere minutes. I encourage you to continue to view these tools as aids to &lt;em&gt;augment&lt;/em&gt; your development process rather than a complete replacement. The true skill to master is knowing when to use these tools and learning how to combine their computational efficiency with your creative problem-solving abilities.&lt;/p&gt;

&lt;p&gt;If you’d like to get started with AI development, we have a great &lt;a href="https://www.vuemastery.com/courses/programming-an-ai-powered-app/analyze-text-feature/" rel="noopener noreferrer"&gt;course&lt;/a&gt; here on Vue mastery to help you get started.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/top-ai-tools-for-developers/" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on April 2, 2025.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>ai</category>
      <category>developertools</category>
      <category>vue</category>
    </item>
    <item>
      <title>Vue Native? Vue + Lynx</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Fri, 28 Mar 2025 10:48:54 +0000</pubDate>
      <link>https://forem.com/vuemastery/vue-native-vue-lynx-e3h</link>
      <guid>https://forem.com/vuemastery/vue-native-vue-lynx-e3h</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%2F51x7mygzchwrv1falumi.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%2F51x7mygzchwrv1falumi.png" alt="Vue Native? Vue + Lynx" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by David Nwadiogbu&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Earlier this month, ByteDance — the company behind TikTok and CapCut — introduced Lynx.js to the world. This could be big news for the Vue community, as it may enable native app development with Vue!&lt;/p&gt;

&lt;p&gt;Lynx is a UI framework built on JavaScript that allows developers to build both web and mobile applications that feel native thereby fulfilling the “&lt;em&gt;write once, run anywhere&lt;/em&gt;” dream frontend/UI developers have pursued for a long time.&lt;/p&gt;

&lt;p&gt;Now, you’ve most likely heard of cross-platform UI frameworks before, so why should this one matter? In this article, we’ll explore Lynx, how it works, and what it means for the future of native mobile apps built with Vue.&lt;/p&gt;

&lt;h3&gt;
  
  
  History of Mobile development with Vue
&lt;/h3&gt;

&lt;p&gt;Before we get into Lynx and why this is an exciting update for the Vue community to keep an eye on, it’s important to discuss other cross-platform solutions that currently exist in the Vue ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Nativescript-Vue
&lt;/h3&gt;

&lt;p&gt;NativeScript-Vue allows developers to create native mobile applications using Vue and Nativescript. It provides direct access to native APIs and platform-specific functionalities while maintaining the familiar Vue syntax and development patterns. Nativescript Vue has been around since 2018 and is fully open source.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ionic Vue with Capacitor
&lt;/h3&gt;

&lt;p&gt;Ionic Vue combines the power of the Ionic Framework with Vue.js to create cross-platform applications using web technologies. While not strictly “native”, Ionic Vue applications can access native device features through Capacitor plugins, making it a popular choice for hybrid mobile development and mobile web/pwa solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Quasar Framework
&lt;/h3&gt;

&lt;p&gt;Quasar is an open-source Vue.js framework that enables developers to build applications for multiple platforms including web, mobile, and even desktop (through Electron) using a single codebase. It provides a rich set of Vue components and utilities, along with built-in support for PWA, Capacitor, and Electron, making it a versatile choice for cross-platform development.&lt;/p&gt;

&lt;p&gt;While these are all great options for building cross-platform apps using Vue, they all come with their many challenges. Native-script uses webpack and doesn’t support modern development tooling like Vite and Rspack by default, Ionic Vue is great for web-like experiences but doesn’t always deliver truly native performance and feel, which is a big deal today. And Quasar, despite its comprehensive feature set, can have a steeper learning curve due to its all-in-one approach.&lt;/p&gt;

&lt;p&gt;These challenges have created opportunity for innovation, particularly in achieving a balance between developer experience and native performance. That’s why Lynx, with its fresh perspective on cross-platform development, shows such promise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Lynx for Mobile Vue Apps?
&lt;/h3&gt;

&lt;p&gt;In the official documentation, Lynx is referred to as an alternative web tailored for app development. Let’s look at some key features that make Lynx a compelling option for Vue developers looking to build performant and scalable mobile applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Lynx is Performance Driven
&lt;/h3&gt;

&lt;p&gt;Lynx is built for performance-first mobile applications and uses a dual-threaded architecture which means that key tasks during development are separated into multiple threads. Namely, the &lt;strong&gt;UI/main&lt;/strong&gt; thread and the &lt;strong&gt;Application/Background&lt;/strong&gt; thread.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let’s break down these terms:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI/main thread&lt;/strong&gt; : This is primarily responsible for rendering the user interface, and handling synchronous UI tasks like event handling. The UI thread is powered by a custom JS engine called Prim JS which was built and optimized specifically for Lynx. It also uses Rspeedy (a toolchain based on the popular Rust-based bundler &lt;a href="https://rspack.dev/" rel="noopener noreferrer"&gt;Rspack&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Background thread:&lt;/strong&gt; This handles your application logic, such as data processing, API calls, and state management.&lt;/p&gt;

&lt;p&gt;By separating these two threads, Lynx makes for a more fluid user experience where complex background tasks with heavy computations don’t block the main thread, thus resulting in instant first frame rendering for the end user.&lt;/p&gt;

&lt;p&gt;This performance improvement can already be seen powering some parts of the Tiktok ecosystem such as its search, studio, and live.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lynx takes a Web-first Approach
&lt;/h3&gt;

&lt;p&gt;Another core feature of Lynx is that it remains true to its web-first approach. Lynx allows you to bring along your existing web development knowledge of building UIs with markup (HTML-like syntax) and native CSS just as you would for the web.&lt;/p&gt;

&lt;p&gt;It takes it a step further by offering direct support for modern CSS features and visual effects like gradients, clipping and masking.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lynx uses Components
&lt;/h3&gt;

&lt;p&gt;Lynx encourages the component way of building UIs as seen in modern JS frameworks like Vue.&lt;/p&gt;

&lt;p&gt;This component-based architecture promotes code reusability and maintainability. Developers can create encapsulated, reusable pieces of UI that manage their own state, making it easier to build and scale complex applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Lynx is framework and platform agnostic
&lt;/h3&gt;

&lt;p&gt;Unlike React Native, Lynx takes a framework-agnostic approach. Lynx’s architecture allows for integration with other popular frontend frameworks like Vue. According to the announcement blog post, frameworks other than React already account for roughly half of Lynx’s total usage.&lt;/p&gt;

&lt;p&gt;This design decision opens up possibilities for the Vue community to leverage Lynx’s powerful features while maintaining their preferred development environment.&lt;/p&gt;

&lt;p&gt;Lynx is not only framework agnostic but also platform agnostic in terms of host platforms and rendering backends. This flexibility allows Lynx to expand to various platforms, including desktop computers, TVs, and IoT devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue + Lynx
&lt;/h3&gt;

&lt;p&gt;Lynx launched with support for React (ReactLynx) as their initial frontend framework, and since then there has been a lot of &lt;a href="https://github.com/lynx-family/lynx/issues/193" rel="noopener noreferrer"&gt;discussions&lt;/a&gt; around creating its Vue counterpart.&lt;/p&gt;

&lt;p&gt;The most popular of these discussions came from this &lt;a href="https://x.com/youyuxi/status/1898663514581168173" rel="noopener noreferrer"&gt;post&lt;/a&gt; on X by Evan You (founder of Vue.js) where he states that the Vue team would be happy to support anyone willing to work on a Vue on Lynx integration.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0BBiJIgP9N34mzGv" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A0BBiJIgP9N34mzGv" width="1024" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xuan Huang, a software engineer on the Lynx team and author of the &lt;a href="https://lynxjs.org/blog/lynx-unlock-native-for-more.html" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; that announced Lynx also &lt;a href="https://x.com/Huxpro/status/1898837980292493721" rel="noopener noreferrer"&gt;shared&lt;/a&gt; the team’s plans to have Vue on Lynx&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A1TbkjdS65M-PE0r2" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2A1TbkjdS65M-PE0r2" width="1024" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in less than 24hrs, Rahul Vashishtha, a member of the Vue community, had started working on a &lt;a href="https://github.com/rahul-vashishtha/lynx-stack/tree/lynx-vue-implementation" rel="noopener noreferrer"&gt;prototype&lt;/a&gt; for the project.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AxG4rUkl8mx9zUQ21" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AxG4rUkl8mx9zUQ21" width="1024" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although, for now, it’s just a prototype, we believe the Vue community will really benefit from a VueLynx integration.&lt;/p&gt;

&lt;p&gt;One of Vue’s enduring strengths that fueled its rapid adoption — both in its early days and today — is its familiar syntax, mirroring standard HTML and CSS, which significantly eased the learning curve. Lynx extends this advantage, providing a similarly intuitive coding environment that minimizes the learning curve for developers already proficient in Vue.&lt;/p&gt;

&lt;p&gt;Although, there’s no official Vue integration yet, you can already start integrating Lynx with your existing Vue app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Vue + Lynx Code
&lt;/h3&gt;

&lt;p&gt;Here is an example of what Vue + Lynx code may look like. This example code is from the Github prototype.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;lynxLogo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/lynx-logo.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello VueLynx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to Vue 3 in Lynx!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;view&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"lynxLogo"&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;'Logo--lynx'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;'Title'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Vue&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;'Subtitle'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;on Lynx&lt;span class="nt"&gt;&amp;lt;/text&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ message }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"increment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Count: {{ count }}&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/view&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This code takes the usual script (with options API) and template format and the only noticeable change here is in the template where we use Lynx-specific markup such as  which is often used for layout capabilities, stylization, and wrapping other elements, the  element is used to display text content and the  element to display images.&lt;/p&gt;

&lt;p&gt;These elements are rendered by Lynx into native UI components for iOS, Android, and web platforms. The relationship between platform-specific views and Lynx elements helps developers understand how to structure their components within this framework&lt;/p&gt;

&lt;p&gt;Vue + Lynx will become a reality after the following build steps have been completed:&lt;/p&gt;

&lt;p&gt;Make Vue compiler work with rspeedy: This step will compile Vue’s SFC to Lynx dual-thread compatible format splitting the template code to the main thread and script code to background thread, extracting style code to the main thread, injecting @lynx-js/css-extract-webpack-plugin, and applying runtime code like hot module reload&lt;/p&gt;

&lt;p&gt;The addition of a new package like vue/runtime-lynx: This step is necessary to rewrite Vue’s DOM operation/runtime code to run on Lynx’s Element API.&lt;/p&gt;

&lt;p&gt;Implement compile-time tools to split code into two threads: This requires additional API change to Vue like supporting script main to compile code to main thread script.&lt;/p&gt;

&lt;p&gt;What’s Next for Vue + Lynx?&lt;br&gt;
This new JS framework seems to have a lot of promise, especially considering its use and implementation on an app like Tiktok, with over 1 billion users globally.&lt;/p&gt;

&lt;p&gt;However, it’s important to note that Lynx is still in its very early stages with no ecosystem, devtools or proper documentation yet. And although it’s said to be “production-ready”, the development team doesn’t recommend that you build your entire app from scratch using the framework, but rather integrate it with already existing apps.&lt;/p&gt;

&lt;p&gt;The development of Vue + Lynx represents an exciting opportunity for Vue developers to build truly native mobile applications while leveraging their existing web development skills. As the ecosystem grows, we can expect to see more tools, components, and best practices emerge that will make Vue Lynx a suitable option for cross-platform development. And as always, Vue Mastery will be here to cover the updates and how to add these new skills to your arsenal.&lt;/p&gt;

&lt;p&gt;With that said, we encourage you to support this development by contributing to the project if you can.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/vue-native-vue-lynx/" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 28, 2025.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>lynx</category>
      <category>frontend</category>
      <category>vue</category>
    </item>
    <item>
      <title>Build a Greeting Card Maker w/ Vue 3 + Satori</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Thu, 19 Dec 2024 10:48:20 +0000</pubDate>
      <link>https://forem.com/vuemasteryteam/build-a-greeting-card-maker-w-vue-3-satori-3ok2</link>
      <guid>https://forem.com/vuemasteryteam/build-a-greeting-card-maker-w-vue-3-satori-3ok2</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%2F9ujwmd7yezfaha9hrm4x.jpeg" 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%2F9ujwmd7yezfaha9hrm4x.jpeg" alt="Build a Greeting Card Maker with Vue 3 and Satori" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by&lt;/em&gt; &lt;a href="https://x.com/dubem_design" rel="noopener noreferrer"&gt;&lt;em&gt;Dubem Izuorah&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the creative developers out there seeking a festive project that leverages your coding skills, let’s build a digital Holiday Greeting Card that you can customize, add your own photo, and instantly download. We’ll be using some cutting-edge web technologies to make this happen, and along the way, you’ll learn about graphics generation, several cool packages, and some neat Vue 3 tricks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we are going to be building
&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AfQ5H0IqPrWDEco4w" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AfQ5H0IqPrWDEco4w" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Screenshot of Holiday Greeting Card Maker interface&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully functional web app for creating personalized holiday cards&lt;/li&gt;
&lt;li&gt;Hands-on experience with Vue 3, Nuxt, and some powerful rendering libraries&lt;/li&gt;
&lt;li&gt;An understanding of how to dynamically generate graphics in the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Initialization
&lt;/h3&gt;

&lt;p&gt;We’ll use Nuxt 3 as our framework because it provides a robust, batteries-included setup for Vue applications. Open your terminal and let’s create our project by running the commands below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;nuxi&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="nx"&gt;christmas&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maker&lt;/span&gt; 
&lt;span class="nx"&gt;cd&lt;/span&gt; &lt;span class="nx"&gt;christmas&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;maker&lt;/span&gt;
&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why Nuxt? It gives us a solid foundation with built-in routing, easy module integration, and an overall easy setup. Perfect for our greeting card maker!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Our Toolkit
&lt;/h3&gt;

&lt;p&gt;Now, we’ll add the libraries that will make our card generation magic happen. Run the following commands on your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;vue&lt;/span&gt;&lt;span class="sr"&gt;/server-renderer @vueuse/&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt; &lt;span class="nx"&gt;nuxt&lt;/span&gt; &lt;span class="nx"&gt;satori&lt;/span&gt; &lt;span class="nx"&gt;satori&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;
&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;nuxtjs&lt;/span&gt;&lt;span class="sr"&gt;/tailwindcs&lt;/span&gt;&lt;span class="err"&gt;s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break down why we’re choosing each of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vuejs.org/guide/scaling-up/ssr.html#server-rendering" rel="noopener noreferrer"&gt;@vue/server-renderer&lt;/a&gt;: Allows us to render Vue components &amp;amp; props as HTML strings - crucial for our SVG generation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vercel/satori" rel="noopener noreferrer"&gt;satori&lt;/a&gt;: Converts our HTML and CSS string into beautiful SVG graphics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/natemoo-re/satori-html" rel="noopener noreferrer"&gt;Satori HTML&lt;/a&gt;: A helper library for rendering HTML content compatible with Satori&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vueuse.org/" rel="noopener noreferrer"&gt;@vueuse/core&lt;/a&gt;: Provides helpful utilities like local storage, watchDebounce and many more&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.nuxtjs.org/getting-started/installation" rel="noopener noreferrer"&gt;@nuxtjs/tailwindcss&lt;/a&gt;: Gives us rapid styling capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuring our Project
&lt;/h3&gt;

&lt;p&gt;Let’s update our nuxt.config.ts to set up our development environment:&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;nuxt.config.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nuxtjs/tailwindcss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re disabling server-side rendering for this client-side app and enabling Tailwind CSS module. This gives us a lightweight, responsive setup perfect for our greeting card maker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding our Card Maker
&lt;/h3&gt;

&lt;p&gt;Now that we’re all setup, let’s dive into creating our Holiday Card Maker step-by-step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1. Implementing the Card Maker Interface
&lt;/h3&gt;

&lt;p&gt;Let’s first focus on creating a simple layout for our card maker. We’ll set up a form with fields for the user to add a name, greeting, and an image upload. We’ll also add a preview area where the card will be displayed.&lt;/p&gt;

&lt;p&gt;Here’s what the base layout will look like:&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-center h-screen items-start bg-gray-100 pt-20"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"border p-8 w-96 bg-white flex flex-col gap-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-xl font-medium"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Christmas Greeting Card Maker&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"border aspect-square banner-here relative"&lt;/span&gt; &lt;span class="na"&gt;v-html=&lt;/span&gt;&lt;span class="s"&gt;"svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"form.name"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-1 border w-full"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"form.greeting"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-1 border w-full"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter greeting"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;".png, .jpg, .jpeg"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;change=&lt;/span&gt;&lt;span class="s"&gt;"handleFileChange"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-1 border w-full"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;small&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Must be an image below 100kb, png, jpeg, or jpg formats only.&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-indigo-500 text-white px-4 py-2"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click.prevent=&lt;/span&gt;&lt;span class="s"&gt;"downloadSvgAsJpeg(svg)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So what’s happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Card Preview:&lt;/strong&gt; The  with v-html="svg" is where our card will appear as an SVG once it’s generated.
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Form Fields:&lt;/strong&gt;&lt;/li&gt;


&lt;ul&gt;
&lt;li&gt;Two text fields: name and greeting. These will dynamically update the card when the user types.&lt;/li&gt;
&lt;li&gt;File input: Allows users to upload an image.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;File Restrictions:&lt;/strong&gt; The small tag below the file input informs users about allowed file size and formats. Large images can slow down rendering, so we’re capping the size at 100KB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download Button:&lt;/strong&gt; This triggers the downloadSvgAsJpeg function, which saves the card as a JPEG image.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2. Setting up Dependencies
&lt;/h3&gt;

&lt;p&gt;Now let’s set up the logic and install the packages needed to power our Holiday Card Maker. Let’s import a few packages that will make things easier for us.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight html"&gt;&lt;code&gt;...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSSRApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderToString&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vue/server-renderer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLocalStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;watchDebounced&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vueuse/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;satori&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;satori&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;satori-html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChristmasCard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/ChristmasCard.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Merry Christmas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fonts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Why do we need this setup?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;createSSRApp&lt;/strong&gt; and &lt;strong&gt;renderToString&lt;/strong&gt; : Render Vue components to HTML strings by virtually mounting the component and its props and rendering out its final output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;useLocalStorage&lt;/strong&gt; : Save form data locally, so users don’t lose progress if they reload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;watchDebounced&lt;/strong&gt; : Helps us prevent performance issues by delaying the processing of user inputs until a certain duration after typing has stopped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;satori&lt;/strong&gt; : Convert HTML to SVG for our graphics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;satori-html&lt;/strong&gt; : Parse HTML strings into a format satori understands.&lt;/li&gt;
&lt;li&gt;The fonts ref uses &lt;strong&gt;useLocalStorage&lt;/strong&gt; to persists the user’s inputs. If you refresh the page, we won’t lose the form input and can continue where we left.&lt;/li&gt;
&lt;li&gt;The svg and fonts variables will store the generated card design and loaded font data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3. Creating the Card Template
&lt;/h3&gt;

&lt;p&gt;Next, let’s create the card template component we imported earlier. This is where the magic happens.&lt;/p&gt;

&lt;p&gt;We’ll use the data from the form to personalize the card. In components/ChristmasCard.vue, we’ll design our card’s visual template. Think of this like designing a canvas where users can personalize their greeting.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;components/ChristmasCard.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full bg-red-500 h-full flex flex-col text-white text-7xl font-bold p-10 items-center justify-center"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"background-image: url(/img/sincerely-media-8EhZobADF8M-unsplash.jpg);"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-bold mb-8 "&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-size: 100px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; {{ greeting }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"photo"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"photo"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rounded-full border-4 border-red-900 mb-8"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width:400px;height:400px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-red-500 px-2 py-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;From&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt; {{ name || 'Add your name here' }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nf"&gt;defineProps&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Here’s what’s happening:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Placeholders:&lt;/strong&gt; &lt;code&gt;{{ greeting }}&lt;/code&gt; and &lt;code&gt;{{ name }}&lt;/code&gt; dynamically display user inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Upload:&lt;/strong&gt; If the user uploads an image, it appears in the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background:&lt;/strong&gt; We’re adding a festive look with a red background image from unsplash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4. Loading Fonts and Initializing the App
&lt;/h3&gt;

&lt;p&gt;Back to the &lt;strong&gt;app.vue&lt;/strong&gt; file, here we have to first load up at least one font file, as Satori requires it to render the graphics. Since our app logic happens on the client, let’s set up some hooks to initialize things and render the graphics for the first time.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Back to app.vue&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadFonts&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;InstrumentSans&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fonts/InstrumentSans-Regular.ttf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
  &lt;span class="nf"&gt;refreshGraphics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;watchDebounced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refreshGraphics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;deep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxWait&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadFonts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Breaking it down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use the onMounted hook, which is auto-imported in Nuxt 3, to load and store the fonts needed for generating the graphics.&lt;/li&gt;
&lt;li&gt;You can use any .ttf font file. To get the same font I used, “Instrument Sans” font, visit &lt;a href="https://fonts.google.com/specimen/Instrument+Sans" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt;, click “Get Font,” then download the font files. Extract the ZIP file, and copy the .ttf font (e.g., InstrumentSans-Regular.ttf) into the public/fonts folder of your Nuxt project. Reference it as /fonts/InstrumentSans-Regular.ttf in your code.&lt;/li&gt;
&lt;li&gt;Inside onMounted, we call refreshGraphics to generate the initial SVG graphics once the font has loaded.&lt;/li&gt;
&lt;li&gt;We use watchDebounced to track changes in the form and update the graphics 500ms after the last update. &lt;em&gt;Note: this is a performance trick.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The loadFonts function loads the required fonts all at once using Promise.all instead of sequentially. &lt;em&gt;Another performance trick to note.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5. Generating SVG from Vue Component
&lt;/h3&gt;

&lt;p&gt;Here’s where we tie everything together. We’ll convert the ChristmasCard component into an SVG.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refreshGraphics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderToHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ChristmasCard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;markup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;satori&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Let me explain:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We first use the renderToHTML utility function to convert the ChristmasCard.vue component imported earlier into HTML string&lt;/li&gt;
&lt;li&gt;Then we are using the html function imported from satori-html earlier to convert the HTML string into a markup format acceptable by the satori package&lt;/li&gt;
&lt;li&gt;Finally, we call satori, passing in the markup, width and height configuration as well as the fonts that we loaded earlier in the onMounted hook&lt;/li&gt;
&lt;li&gt;The resulting svg string is stored in the svg.value ref, which we used in the &lt;strong&gt;app.vue&lt;/strong&gt; template section to display the svg on the viewport. See  in the template section&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6. Utility Functions
&lt;/h3&gt;

&lt;p&gt;Let’s wrap up everything we’ve been working on by adding some essential utility functions for our code and template features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding Image Upload Support&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re adding functionality to handle image uploads. This will ensure that users can select an image, check if it’s within the size limit (100KB), and display it.&lt;/p&gt;

&lt;p&gt;Paste this below the refreshGraphics code:&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleFileChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;File size must be below 100kb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Why check file size?&lt;/strong&gt; We’re limiting the file size to ensure smooth performance. Anything larger could slow things down, so 100KB is a safe upper limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting Vue component to HTML string
&lt;/h3&gt;

&lt;p&gt;Next, we’ll render the Vue component as an HTML string. This allows us to use Vue for server-side rendering, email templates, or static site generation. In this case, it’s for generating a greeting card template. Let’s add this as well to our code.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderToHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createSSRApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;createSSRApp&lt;/strong&gt; : This function is used to create a server-side rendered (SSR) Vue application instance, which can be rendered to an HTML string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;renderToString&lt;/strong&gt; : This renders the Vue component (GreetingCard) to an HTML string, passing any props needed by the component.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Downloading the Card as a JPEG
&lt;/h3&gt;

&lt;p&gt;Finally, let’s add code that will enable us to download our card as a JPEG.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;downloadSvgAsJpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;svgString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image.jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;svgString&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/svg+xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Why does this work?&lt;/strong&gt; By using HTML Canvas API, we can draw the SVG onto the canvas and convert it into a JPEG for easy downloading. It’s a quick and efficient way to generate image files from vector graphics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7. Styling
&lt;/h3&gt;

&lt;p&gt;To ensure the SVG element is displayed correctly within the container, we need to apply some CSS styling. Since the generated SVG has a fixed size of 1080px by 1080px, but the parent container is smaller, we need to scale the SVG to fit inside the container without distortion.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;app.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight html"&gt;&lt;code&gt;...

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.banner-here&lt;/span&gt; &lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This CSS rule ensures that the SVG element inside the .banner-here div is resized to fill the available space while maintaining its aspect ratio.&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AEiV3cbZTqXMrgA4E" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AEiV3cbZTqXMrgA4E" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By now, your project should look something like this screenshot. Let’s run it to see the real magic!&lt;/p&gt;

&lt;h3&gt;
  
  
  Running our code
&lt;/h3&gt;

&lt;p&gt;To see our app in action, run the command below and open &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; on your browser.&lt;/p&gt;

&lt;p&gt;For reference, here is the &lt;a href="https://github.com/Code-Pop/build-a-greeting-card-maker-with-vue-3-and-satori" rel="noopener noreferrer"&gt;Github repo for this tutorial&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fcdn-images-1.medium.com%2Fmax%2F656%2F0%2AtxocqIkxj4QrLfFY" 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%2Fcdn-images-1.medium.com%2Fmax%2F656%2F0%2AtxocqIkxj4QrLfFY" width="656" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see something resembling the interface in the GIF above. You can edit the details, add an image, and download your image. Congrats! You now have your own personal Holiday Card maker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;You’ve just built a personalized greeting card maker! 🎉 But don’t stop here. Experiment with different designs, add more customization options, or even create cards for other occasions.&lt;/p&gt;

&lt;p&gt;Some ideas to expand your project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more background themes&lt;/li&gt;
&lt;li&gt;Include text color/font selection&lt;/li&gt;
&lt;li&gt;Create templates for birthdays, anniversaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to do more on the client side? Discover how to render 3d objects in the browser by reading &lt;a href="https://www.vuemastery.com/blog/building-a-3d-scene-in-nuxt-with-tresjs/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/build-a-greeting-card-maker-w-vue-3-satori" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 19, 2024.&lt;/em&gt;&lt;/p&gt;





&lt;/ol&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>vue</category>
    </item>
    <item>
      <title>What’s next for Vue in 2025?</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Wed, 18 Dec 2024 10:00:15 +0000</pubDate>
      <link>https://forem.com/vuemasteryteam/whats-next-for-vue-in-2025-17gk</link>
      <guid>https://forem.com/vuemasteryteam/whats-next-for-vue-in-2025-17gk</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%2Ftuj3wiemooo4a7sic4cp.jpeg" 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%2Ftuj3wiemooo4a7sic4cp.jpeg" alt="Vue in 2025" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Andy Li&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As we head into 2025, staying on top of the Vue ecosystem isn’t just about keeping up — it’s about gaining the competitive edge that comes with mastering the latest features, performance improvements, and development workflows that will shape the future of Vue applications, including being prepared for any changing APIs of the tools we use.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore the upcoming releases of key Vue tools: Nuxt v4, Vite v6, Vitest v3, and Pinia v3. We’ll also examine the current state of Vue’s Vapor mode.&lt;/p&gt;

&lt;p&gt;Let’s explore the key updates coming for Vue developers. You’ll understand how these tools are evolving and how to prepare your development workflow for 2025!&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuxt 4
&lt;/h3&gt;

&lt;p&gt;While Nuxt v4 doesn’t have a confirmed release date yet, you can already &lt;a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now" rel="noopener noreferrer"&gt;try out its features&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Most of the Nuxt v4 features are already available in the current version of Nuxt v3 — you just need to opt into them through the Nuxt config file:&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;/nuxt.config.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compatibilityVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you’re using Nuxt v3.12 or higher to enable these features. To see the complete list of available Nuxt 4 features, check out my article &lt;a href="https://www.vuemastery.com/blog/upgrading-to-nuxt-4" rel="noopener noreferrer"&gt;Upgrading to Nuxt 4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Even without updating the config, you can access some Nuxt 4 features by default in v3.13 or higher. I cover two of these features — Nuxt Route Groups and Custom Fetch Trigger — in my article &lt;a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now" rel="noopener noreferrer"&gt;Nuxt 4 features you can use now&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite 6
&lt;/h3&gt;

&lt;p&gt;Vite is a tool that many frameworks depend on, powering the build process of countless modern web apps. The recently released Vite v6 introduces the Environment API — a feature that standardizes how JavaScript runs across different environments (client, server, edge, etc.). While most developers won’t use the Environment API directly, it serves primarily as a tool for framework and plugin creators.&lt;/p&gt;

&lt;p&gt;Another recent important topic of Vite is the transition from Rollup to Rolldown. Set to replace Rollup in Vite’s internal achitecture, Rolldown is a new build tool that promises significantly faster build times and better memory efficiency, which will be especially beneficial for large-scale applications. For those who are wondering, Vite v6 is still using Rollup as its bundler.&lt;/p&gt;

&lt;p&gt;If you want to learn more about Vite and Rolldown, you can check out &lt;a href="https://www.vuemastery.com/blog/the-build-process-of-a-vue-app-rollup-vs-rolldown" rel="noopener noreferrer"&gt;the Build Process of a Vue app: Rollup vs Rolldown article&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vitest 3
&lt;/h3&gt;

&lt;p&gt;Vitest is currently at v2.1. Since it’s built on top of Vite, Vitest will release v3 in January 2025 to align with Vite’s new version. In terms of functionality, Vitest v3 is essentially equivalent to what would have been “Vitest v2.2.”&lt;/p&gt;

&lt;p&gt;The Vitest Node API — which lets you run tests through a Node.js program — remains experimental in v2.1. While it will stay experimental in v3.0, the Vitest team plans to make it a stable feature in v3.1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pinia 3
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.vuemastery.com/courses/pinia-fundamentals/fundamentals-what-is-pinia" rel="noopener noreferrer"&gt;Pinia&lt;/a&gt; is the official store management tool for Vue.js. The best thing about Pinia is that its API has been stable since v1, and v3 is no exception.&lt;/p&gt;

&lt;p&gt;You still define a store with defineStore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pinia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSampleStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can still set your state, getters, and actions the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSampleStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sample&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;getters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can still use the Composition API style syntax to do the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSampleStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sample&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uppercase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The biggest change of this new version is that it will drop support for Vue v2. So this means, if you plan to use Pinia v3, your app would have to be running Vue v3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vapor Mode
&lt;/h3&gt;

&lt;p&gt;Vue’s Vapor mode is still under research and development.&lt;/p&gt;

&lt;p&gt;If you haven’t heard of it, it’s a version of Vue.js that doesn’t rely on Virtual DOM for rendering. Every time the state of a component is changed, Vue.js will create a Virtual DOM (VDOM) to represent the component’s new rendered result. Then, the VDOM is compared to the VDOM from the previous rendering in a process called &lt;em&gt;diffing&lt;/em&gt; to figure out what has to be updated in the actual DOM.&lt;/p&gt;

&lt;p&gt;Vapor Mode is designed to eliminate this Virtual DOM creation and the diffing steps, in order to make the reactive process faster.&lt;/p&gt;

&lt;p&gt;By default, Vue.js with Virtual DOM is already fast. However, if you have too many reactive elements on the same page, and they have to be updated frequently, the user experience can get laggy at times. This is the use case for which Vapor mode is being created.&lt;/p&gt;

&lt;p&gt;Vapor mode basically compiles your source code into a runtime version of the code that is already aware of what has to be updated when something is changed. More work is done in compile time so that less work has to be done in runtime.&lt;/p&gt;

&lt;p&gt;Although Vapor Mode is still in development, you can experiment with it through the &lt;a href="https://github.com/vuejs/vue-vapor" rel="noopener noreferrer"&gt;vue-vapor repo&lt;/a&gt;. Since it’s designed as a drop-in performance upgrade, you won’t need to modify your Vue.js components to benefit from the faster rendering — though your components must use the Composition API syntax.&lt;/p&gt;

&lt;p&gt;On Vue Mastery, you can soon check out our Vapor Mode course taught by Vue Creator Evan You!&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s Next for Vue in 2025
&lt;/h3&gt;

&lt;p&gt;While 2025 doesn’t seem poised to drastically disrupt your Vue development workflow, we can expect positive evolution of the tools we already know and use. The Vue ecosystem has been steadily stabilizing, with fewer syntax changes compared to previous years. This stability means you won’t need to worry about your code breaking when installing new versions.&lt;/p&gt;

&lt;p&gt;Staying tuned into the latest tools, workflows, and best practices in the Vue ecosystem ensures we stay on the leading edge of our career field. Our library of Vue courses, conferences talks and articles can help you stay at the top of your game, and right now you can &lt;a href="https://www.vuemastery.com/pricing/?coupon=HOLIDAY-24" rel="noopener noreferrer"&gt;get an entire year’s subscription for 50% off&lt;/a&gt; &lt;strong&gt;!&lt;/strong&gt; Become the best Vue developer you can be in 2025.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/whats-next-for-vue-in-2025" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 18, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>vue</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Build Process of a Vue App: Rollup vs Rolldown</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Wed, 04 Dec 2024 11:00:36 +0000</pubDate>
      <link>https://forem.com/vuemastery/the-build-process-of-a-vue-app-rollup-vs-rolldown-2nfj</link>
      <guid>https://forem.com/vuemastery/the-build-process-of-a-vue-app-rollup-vs-rolldown-2nfj</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%2F35kjfpx4pv0ud3i1gpvy.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%2F35kjfpx4pv0ud3i1gpvy.png" alt="The Build Process of a Vue App: Rollup vs Rolldown" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Andy Li&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A build tool is essential for modern web development, acting as the engine that transforms your raw code into something browsers can understand and execute efficiently. Without a fast, reliable build tool, you could face frustratingly long startup times when launching your development server, and even simple code changes could take precious seconds to reflect in the browser—disrupting your development flow and productivity.&lt;/p&gt;

&lt;p&gt;In this article, we'll talk about all the things that are involved in the build process of a Vue.js app (or a React.js app), including Vite, esbuild, Rollup, and an up-and-coming tool that is being built by Evan You (creatore of Vue.js and Vite.js) called &lt;em&gt;Rolldown&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;First, let's start with Vite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vite's Current Build Process
&lt;/h2&gt;

&lt;p&gt;Like many other frameworks, Vue.js is using Vite as its build tool.&lt;/p&gt;

&lt;p&gt;Vite has two different modes: development and production. They're implemented differently behind the scene.&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%2Fa5efrlwcydd9erumlusj.jpg" 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%2Fa5efrlwcydd9erumlusj.jpg" alt="vite.png" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vite uses different build strategies for development versus production environments to optimize for their unique needs. During development, the focus is on providing rapid feedback and fast hot module replacement (HMR) for a smooth developer experience. In production, the priority shifts to generating optimized, performant bundles for end users.&lt;/p&gt;

&lt;p&gt;Let's explore how Vite handles development builds, where its innovative approach really shines.&lt;/p&gt;




&lt;h3&gt;
  
  
  Vite in Development Mode
&lt;/h3&gt;

&lt;p&gt;In development mode, build speed is crucial. Rather than bundling code after every change, Vite serves the modified files directly in ESM format (like Vue component's &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; code), allowing for rapid browser updates with minimal processing.&lt;/p&gt;

&lt;p&gt;In practice, Vite transforms TypeScript code into browser-compatible JavaScript. While the development build requires fewer steps than production (which we'll explore shortly), converting code quickly is still demanding—especially since changes need to appear in the browser within seconds.&lt;/p&gt;

&lt;p&gt;That's why Vite employs esbuild, a high-performance tool written in Go. Since Go compiles directly to machine language (the low-level binary instructions that processors can execute), esbuild delivers exceptional speed - a crucial requirement for modern development server setups.&lt;/p&gt;




&lt;h3&gt;
  
  
  Vite in Production Mode
&lt;/h3&gt;

&lt;p&gt;While build speed matters in production, it's not the top priority. When creating a production build, you can expect some waiting time regardless of your build tool. Unlike development mode—which only processes one file and its dependencies at a time—production builds must bundle all code from scratch, making instant builds impossible.&lt;/p&gt;

&lt;p&gt;Additionally, production builds often require extra steps. For example, you may need to transpile JavaScript code for older browsers—something unnecessary during development when you can simply use a modern browser.&lt;/p&gt;

&lt;p&gt;Other crucial production build steps include minification (removing unnecessary characters from code while preserving functionality) and code splitting (breaking code into smaller chunks that load on demand). These processes directly impact the code's runtime performance.&lt;/p&gt;

&lt;p&gt;The top priority for a production build is ensuring the code runs efficiently in users' browsers. This takes precedence over build speed since production builds happen occasionally, while users access your site frequently.&lt;/p&gt;

&lt;p&gt;Vite chose Rollup for production bundling because of its rich plugin ecosystem, which could be used directly with Vite. Since Vite's plugin architecture mirrors Rollup's, this compatibility helped foster a thriving plugin community and contributed to Vite's widespread adoption.&lt;/p&gt;

&lt;p&gt;&lt;span&gt;💡&lt;/span&gt;&lt;br&gt;
  &lt;strong&gt;So to summarize:&lt;/strong&gt; Vite uses &lt;code&gt;esbuild&lt;/code&gt; (optimized for speed) for development builds and &lt;code&gt;Rollup&lt;/code&gt; (known for its plugins) for production builds.&lt;/p&gt;

&lt;p&gt;While this setup has worked well, a major change is coming: Vite will soon replace both esbuild &lt;strong&gt;and&lt;/strong&gt; Rollup with a new tool called &lt;em&gt;Rolldown&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Before exploring Rolldown, let's examine the challenges with Vite's current setup that led to Rolldown’s invention.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problems with Vite
&lt;/h2&gt;

&lt;p&gt;When a build tool or a bundler processes your source code, it first creates an abstract syntax tree (AST) - a tree-like data structure that represents your code's syntax and structure. For example, a function declaration would become a "FunctionDeclaration" node in the AST, with child nodes for its parameters and body.&lt;/p&gt;

&lt;p&gt;Using two different tools to parse the same source code is inefficient for code transformation, since a single tool (or set of compatible tools) could reuse the parsed results throughout the process.&lt;/p&gt;

&lt;p&gt;While esbuild handles Vite's development mode, it also plays a role in production mode through minification. This means esbuild and Rollup operate sequentially in the build process. For React applications, the process becomes even more complex with the addition of SWC (another build tool written in Rust) alongside esbuild and Rollup.&lt;/p&gt;

&lt;p&gt;Using multiple tools in sequence creates inefficiencies, as each tool must parse the code independently and pass results between them. While Rollup's robust plugin ecosystem makes it valuable for production builds, its slower speed compared to esbuild presents a trade-off. The ideal solution would be a tool that combines esbuild's performance with Rollup's extensibility—and this is precisely what Rolldown aims to achieve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction to Rolldown
&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%2Fc2ywcsutvdwhh7wvwxdr.jpg" 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%2Fc2ywcsutvdwhh7wvwxdr.jpg" alt="rolldown.png" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rolldown is a bundler written in Rust—a programming language that, like Go, compiles directly to machine code. This means Rolldown will achieve speeds similar to esbuild.&lt;/p&gt;

&lt;p&gt;Since Rolldown shares a similar plugin API with Rollup, most existing Vite-compatible plugins will work seamlessly with the new Rolldown-powered version of Vite.&lt;/p&gt;

&lt;p&gt;In essence, you'll be able to use Vite the same way you do now, just with improved performance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So how much faster are we talking?&lt;/em&gt; If speed is the main selling point, it needs to be significantly faster to justify adopting a new tool, right?&lt;/p&gt;

&lt;p&gt;According to benchmarks by Evan You, when testing the bundling speed of Vue's core source code, Rolldown's build setup performs over 7x faster than the Rollup-based setup.&lt;/p&gt;

&lt;p&gt;This benchmark specifically measures production build time. &lt;em&gt;What about development?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Currently, Vite uses esbuild for development. According to Evan You's benchmarks, Rolldown performs almost twice as fast as esbuild. This improvement is more modest than the Rollup comparison since esbuild is already highly performant.&lt;/p&gt;

&lt;p&gt;All of these performance upgrades are powered by a suite of underlying tools in Rolldown. These include a new parser, linter, resolver, transformer and more—all part of the Oxc project (created by the Rolldown team).&lt;/p&gt;

&lt;p&gt;Oxc is a set of JavaScript language tools. Although these tools are used in Rolldown, they can also be used individually in a non-Rolldown context. For instance, the Oxc linter is faster than ESLint, so it can be used in your CI workflow to catch code errors quickly before running ESLint.&lt;/p&gt;

&lt;p&gt;Oxc tools are generally faster than other similar tools because they were written from scratch with performance as their primary goal.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Future of Vite
&lt;/h2&gt;

&lt;p&gt;While Rolldown is not yet production-ready, an alpha release of Vite featuring Rolldown integration is expected before the end of 2024. We'll continue reporting on the latest developments in Vue, Vite, and Rolldown in the coming months.&lt;/p&gt;

&lt;p&gt;If you'd like to learn more about Vite, check out the course &lt;a href="https://www.vuemastery.com/courses/lightning-fast-builds-with-vite/intro-to-vite" rel="noopener noreferrer"&gt;Lightning Fast Builds&lt;/a&gt; here on Vue Mastery, taught by Vite's creator Evan You himself.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/the-build-process-of-a-vue-app-rollup-vs-rolldown" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on December 4, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>javascript</category>
      <category>vitejs</category>
      <category>vue</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Nuxt 4 features you can use now</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Thu, 12 Sep 2024 10:48:39 +0000</pubDate>
      <link>https://forem.com/vuemastery/nuxt-4-features-you-can-use-now-1656</link>
      <guid>https://forem.com/vuemastery/nuxt-4-features-you-can-use-now-1656</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mse30ldhfgv6e8suffv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mse30ldhfgv6e8suffv.jpeg" alt="Nuxt 4 Features you can use now" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Andy Li&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Although Nuxt v4 is not out yet, there are some Nuxt 4 features (originally planned for Nuxt 4 release) being released in the latest minor version 3.13. We’re going to explore two of the bigger features from this release, namely, &lt;em&gt;route groups&lt;/em&gt; and &lt;em&gt;custom prefetch trigger&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuxt Route Groups
&lt;/h3&gt;

&lt;p&gt;As you know, Nuxt.js routing is file-based routing. This means you don’t have to set up routing config, all you have to do is to name your file accordingly and put it in the right folder. The page URLs are mapped to the file names of the page components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m7e5l0T7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AlShtmAgiFUk86cSJ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m7e5l0T7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AlShtmAgiFUk86cSJ" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But if you put all the files on the same level like this, it could be difficult to see how they’re related.&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;about&lt;/strong&gt; and &lt;strong&gt;contact&lt;/strong&gt; are information-related while &lt;strong&gt;signup&lt;/strong&gt; and &lt;strong&gt;login&lt;/strong&gt; are authentication-related, the file structure would be more expressive if they are grouped like this with folders:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--st5_Bb8e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2APpa9G3YEI1llTCsf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--st5_Bb8e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2APpa9G3YEI1llTCsf" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the problem with this is that you might not want the extra prefix on the URL path.&lt;/p&gt;

&lt;p&gt;The solution for this problem is called Route Groups. With the above example, the folders just have to be named with parentheses:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NL4o1pDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AhkNWra2V8iU5VuEE" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NL4o1pDQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AhkNWra2V8iU5VuEE" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now all these pages can still be accessed the same way. Basically, Nuxt just ignores the folders named with parentheses.&lt;/p&gt;

&lt;p&gt;This is just an example. You can imagine if you have more pages in your projects, this more expressive way of structuring your files is valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Prefetch Triggers
&lt;/h3&gt;

&lt;p&gt;Prefetching refers to loading the next page while the user is visiting the current page, so when the user actually clicks the next page, it will be loaded faster.&lt;/p&gt;

&lt;p&gt;Specifically in Nuxt, only the page component code will be prefetched. This means if your component is fetching data from an API, that data will not be prefetched.&lt;/p&gt;

&lt;p&gt;In Nuxt.js, prefetching is enabled by default if you use :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/signup"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;signup&lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the signup page will now be prefetched when this link is visible on screen the first time.&lt;/p&gt;

&lt;p&gt;This means if multiple links to different pages appear on the screen, all of these pages will be prefetched — even if most of them won’t be visited.&lt;/p&gt;

&lt;p&gt;This is very wasteful. So, &lt;em&gt;when to trigger the prefetch&lt;/em&gt; becomes the key.&lt;/p&gt;

&lt;p&gt;Nuxt now offers two triggers for prefetching:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fGx_lqCU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2A84yWYaNBqIurOdwM" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fGx_lqCU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2A84yWYaNBqIurOdwM" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;visibility&lt;/em&gt; trigger is the default configuration.&lt;/p&gt;

&lt;p&gt;You can set the &lt;em&gt;interaction&lt;/em&gt; trigger like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/signup"&lt;/span&gt; &lt;span class="na"&gt;prefetch-on=&lt;/span&gt;&lt;span class="s"&gt;"interaction"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;signup&lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it’s technically possible to use both triggers on the same link, this offers little practical benefit. Any link that can be hovered is already visible on the screen — otherwise, you wouldn’t be able to hover over it in the first place.&lt;/p&gt;

&lt;p&gt;In practice, you’ll likely have some links you want to prefetch by &lt;em&gt;visibility&lt;/em&gt; and others by &lt;em&gt;interaction&lt;/em&gt;. But as an option, you can set one trigger for the entire app in the config file:&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;/nuxt.config.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;nuxtLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;prefetchOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we’re setting the global default to be prefetched by interaction. Note that visibility is set to true by default, so we had to set it back to false to disable it.&lt;/p&gt;

&lt;p&gt;Once again, visibility is the default, so you don’t need to add anything to the config file if you just want to prefetch by visibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Features
&lt;/h3&gt;

&lt;p&gt;We’ve only covered two of the more important features from Nuxt v3.13. You can find the full list of features in &lt;a href="https://nuxt.com/blog/v3-13" rel="noopener noreferrer"&gt;the official Nuxt Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now/" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on September 12, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>vue</category>
      <category>nuxt</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Dynamic Layouts with Vue jsx: A Guide to Flexible and Maintainable UIs</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Thu, 05 Sep 2024 10:48:54 +0000</pubDate>
      <link>https://forem.com/vuemastery/dynamic-layouts-with-vue-jsx-a-guide-to-flexible-and-maintainable-uis-2l57</link>
      <guid>https://forem.com/vuemastery/dynamic-layouts-with-vue-jsx-a-guide-to-flexible-and-maintainable-uis-2l57</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n3z2oavb9izvl3kcvyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n3z2oavb9izvl3kcvyl.png" alt="Dynamic Layouts with Vue jsx" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by&lt;/em&gt; &lt;a href="https://x.com/dubem_design" rel="noopener noreferrer"&gt;&lt;em&gt;Dubem Izuorah&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever spent hours tweaking the same web layout across multiple pages or struggled to make your UI adapt to changing data without breaking? These common challenges can slow down your development process and lead to frustrating, error-prone code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing Dynamic Layouts
&lt;/h3&gt;

&lt;p&gt;By creating layouts at runtime based on data, you can build flexible, maintainable, and scalable applications that adapt effortlessly to changes. This approach,called &lt;strong&gt;Dynamic layouts&lt;/strong&gt; , eliminates the need for repetitive HTML edits, allowing your UI to stay in sync with your data seamlessly.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how dynamic layouts can transform your development workflow and elevate your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use Dynamic Layouts?
&lt;/h3&gt;

&lt;p&gt;Dynamic layouts are particularly beneficial when content frequently changes or when displaying responsive UIs that pull from APIs, other data sources, or respond to user interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Dynamic Layouts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt; : Easily adjust UI components based on underlying data without modifying HTML or template code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt; : Adhere to the DRY (Don’t Repeat Yourself) principle, reducing repetitive code and simplifying maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; : Manage your UI programmatically, allowing for easy updates, additions, or removals of items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Reduction&lt;/strong&gt; : Minimize manual HTML edits, decreasing the likelihood of errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Responsiveness&lt;/strong&gt; : Dynamically show or hide UI elements based on conditions, making the interface more responsive to user interactions or data changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical Scenarios for Dynamic Layouts
&lt;/h3&gt;

&lt;p&gt;Dynamic layouts shine in various real-world scenarios, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forms&lt;/strong&gt; : For form-heavy apps, it might be best to abstract you form fields into a computed property paired with some validation library. In the layout you can loop through your property to conditionally show form fields and properties like labels, class names, validation messages etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog Post Lists&lt;/strong&gt; : Manage and update each post card’s content dynamically based on its data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Blocks&lt;/strong&gt; : Organize and display diverse content elements efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Lists, Navigation Items, Dashboard Panels&lt;/strong&gt; : Ensure consistent styling and easy adjustments without manual HTML updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website Sections&lt;/strong&gt; : Dynamically generate sections to maintain a cohesive and easily adjustable layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By generating these elements dynamically, you ensure consistency, simplify adjustments, and maintain a clean codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Dynamic Navbar
&lt;/h3&gt;

&lt;p&gt;Let’s gain hands-on experience with dynamic layouts by building a dynamic navbar. For reference, here is the &lt;a href="https://github.com/Code-Pop/Dynamic-Layouts" rel="noopener noreferrer"&gt;Github repo for this tutorial.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Imagine you’re developing a payment platform and need to create a clean, extensible navbar component as shown in the screenshots below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X1Gl8Wpo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AgtVp1TKqACbv3318" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X1Gl8Wpo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AgtVp1TKqACbv3318" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navbar when a user is logged in&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GrkSwEcO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AvxQBAJ24KQW37ShT" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GrkSwEcO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/0%2AvxQBAJ24KQW37ShT" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Navbar with no user logged in&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the Environment
&lt;/h3&gt;

&lt;p&gt;To focus on dynamic layouts, we won’t delve into environment setup details here. You can find the complete code samples in our &lt;a href="https://github.com/Code-Pop/Dynamic-Layouts" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technologies Used:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://nuxt.com/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; — Vue framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tailwindcss.nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt TailwindCSS&lt;/a&gt; — Styling&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/phosphor-icons/vue" rel="noopener noreferrer"&gt;Phosphor-icons Vue&lt;/a&gt; — Icon components&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Static Approach
&lt;/h3&gt;

&lt;p&gt;A static Navbar component involves numerous HTML tags and properties, making the code hard to read and maintain.&lt;/p&gt;

&lt;p&gt;Updating such a layout is error-prone, especially with repetitive elements sharing the same properties or styling.&lt;/p&gt;

&lt;p&gt;While a static approach is useful for drafting layouts, dynamic layouts are preferable for maintainability and collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of a Static Navbar:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;App.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-screen border-b py-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto flex items-center gap-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Logo --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://logo.clearbit.com/google.com"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-10 h-10 object-contain"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Dashboard or Home Link --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PhGauge&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dashboard&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PhHouseSimple&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Spacer --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Add Funds Button (Visible only when user is authenticated) --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-white bg-green-500 px-4 py-2 rounded-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add Funds&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- User Avatar (Visible only when user is authenticated) --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Avatar&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://randomuser.me/api/portraits/men/40.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Dubem Izuorah&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Auth Button --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"user = !user"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;{{ user ? 'Logout' : 'Login' }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-64 bg-gray-100 flex justify-center pt-10 text-xl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;App Here&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"jsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NuxtLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Avatar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PhGauge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PhHouseSimple&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@phosphor-icons/vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Simulate user authentication status&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While static layouts may suffice for simple interfaces, dynamic layouts offer superior maintainability and scalability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Approach
&lt;/h3&gt;

&lt;p&gt;To create a dynamic navbar, we’ll want to define our layout data and generate UI components based on this data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Data
&lt;/h3&gt;

&lt;p&gt;Instead of hard-coding each menu item, we can create a list of menu items in our script with properties like title, image, to, icon, render, and class. This allows us to dynamically generate the navbar based on the data.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;App.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"jsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Import necessary components&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NuxtLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Avatar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PhGauge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PhHouseSimple&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@phosphor-icons/vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Simulate user authentication status&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Define menu items&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://logo.clearbit.com/google.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;PhGauge&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhHouseSimple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;spacer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ml-auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add-funds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-white bg-green-500 px-4 py-2 rounded-lg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Funds&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Avatar&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://randomuser.me/api/portraits/men/40.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dubem Izuorah&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Logout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// Filter out hidden items&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menuItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reactive Layout&lt;/strong&gt; : Use computed properties to update the layout based on reactive data like user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional Rendering&lt;/strong&gt; : Apply different values using ternary operators based on state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSX Integration&lt;/strong&gt; : Utilize JSX to include HTML elements and Vue components directly within property values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Properties&lt;/strong&gt; : Use properties like hide to conditionally display items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Handling&lt;/strong&gt; : Store functions or event handlers (e.g., onClick) within data objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular Data Management&lt;/strong&gt; : Move layout data to Vue composables or external JSON files for better organization.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Defining the Avatar Component
&lt;/h3&gt;

&lt;p&gt;Create an Avatar component used within the dynamic navbar.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;Avatar.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rounded-full w-10 h-10 bg-gray-100 overflow-hidden"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-full object-cover"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component creates a reusable avatar element that can display different images based on the ‘src’ prop passed to it. This makes it flexible and easy to use throughout your application for displaying user avatars or profile pictures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Layout
&lt;/h3&gt;

&lt;p&gt;Use the defined data to render the navbar dynamically.&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;App.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-screen border-b py-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto flex items-center gap-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt;
        &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"item in menuItems"&lt;/span&gt;
        &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"item.key"&lt;/span&gt;
        &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"item.to ? NuxtLink : 'button'"&lt;/span&gt;
        &lt;span class="na"&gt;v-bind=&lt;/span&gt;&lt;span class="s"&gt;"item"&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"item.onClick"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center"&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"item.image"&lt;/span&gt; &lt;span class="na"&gt;:src=&lt;/span&gt;&lt;span class="s"&gt;"item.image"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-10 h-10 object-contain"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"item.render"&lt;/span&gt; &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"item.render"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"item.icon"&lt;/span&gt; &lt;span class="na"&gt;:is=&lt;/span&gt;&lt;span class="s"&gt;"item.icon"&lt;/span&gt; &lt;span class="na"&gt;:size=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"item.title"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ml-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ item.title }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/component&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-64 bg-gray-100 flex justify-center pt-10 text-xl"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;App Here&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"jsx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// The script remains the same as above&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;v-bind Usage&lt;/strong&gt; : Attach multiple properties to elements simultaneously, keeping the template clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique Keys&lt;/strong&gt; : Use the key attribute to prevent rendering issues during dynamic updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Components&lt;/strong&gt; : Utilize Vue’s dynamic components to render different elements based on data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Binding&lt;/strong&gt; : Attach events like &lt;a class="mentioned-user" href="https://dev.to/click"&gt;@click&lt;/a&gt; dynamically based on data properties.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Component Modularity&lt;/strong&gt; : Break down components further and import them as needed for better scalability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following this approach, you create a dynamic and flexible horizontal navbar that’s easy to extend or modify. This method not only saves time and effort but also ensures your codebase remains clean and maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhancing Flexibility with Vue JSX
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vuejs.org/guide/extras/render-function" rel="noopener noreferrer"&gt;Vue JSX&lt;/a&gt; allows developers to write Vue components using a syntax similar to React’s JSX, offering greater flexibility and expressiveness. Unlike Vue’s typical template-based approach, JSX allows you to embed HTML-like elements directly inside JavaScript logic. This is particularly useful in dynamic layouts where you need to generate or manipulate UI elements based on dynamic data.&lt;/p&gt;

&lt;p&gt;Instead of separating logic and markup across different sections of the component (template and script), JSX enables you to manage both in one cohesive block of code.&lt;/p&gt;

&lt;p&gt;In our solution, JSX helps simplify how we dynamically render components like the Avatar or conditional elements such as the “Add Funds” button.&lt;/p&gt;

&lt;p&gt;For example, instead of hardcoding these elements into a template, we can dynamically generate them using a render function in JavaScript.&lt;/p&gt;

&lt;p&gt;This allows us to update components like the navbar with real-time data, such as user authentication status, without duplicating code or scattering logic across the template and script sections.&lt;/p&gt;

&lt;p&gt;By using JSX, we maintain cleaner, more maintainable code, while still leveraging Vue’s reactivity and flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Now that we understand dynamic layouts, it’s time to put your knowledge into practice. Here are some suggested next steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Refactor Existing Projects&lt;/strong&gt; : Identify and refactor parts of your current projects where dynamic layouts can enhance efficiency and readability, especially in areas with repetitive HTML or component logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrate with CMS&lt;/strong&gt; : Experiment with integrating a CMS or other systems with your dynamic layouts to create even more dynamic and responsive user interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborate with Your Team&lt;/strong&gt; : Share your insights on dynamic layouts with your team. Discuss how you can collectively leverage dynamic layouts in your projects to boost productivity and foster a culture of continuous improvement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore Advanced Use Cases&lt;/strong&gt; : Continue exploring more advanced applications of dynamic layouts. Mastery comes from consistent practice and exploration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By taking these steps, you’re well on your way to becoming proficient in dynamic layouts. For an even smoother experience in creating dynamic interfaces, check out our course on &lt;a href="https://www.vuemastery.com/courses/easy-interfaces-with-vuetify/intro-to-vuetify" rel="noopener noreferrer"&gt;Easy Interfaces with Vuetify&lt;/a&gt;, the popular component library for Vue.js developers.&lt;/p&gt;

&lt;p&gt;By embracing dynamic layouts, you can streamline your development process, enhance your application’s flexibility, and maintain a cleaner, more maintainable codebase. Start integrating dynamic layouts into your projects today and experience the transformative impact on your workflow and application quality.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/dynamic-layouts-with-vue-jsx" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on September 5, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>javascript</category>
      <category>vue</category>
      <category>developertools</category>
    </item>
    <item>
      <title>Minimalist Nuxt Authentication</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Fri, 12 Jul 2024 10:48:53 +0000</pubDate>
      <link>https://forem.com/vuemastery/minimalist-nuxt-authentication-4094</link>
      <guid>https://forem.com/vuemastery/minimalist-nuxt-authentication-4094</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AOsOcaoKo8NYdVnG7FmYxEw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F1%2AOsOcaoKo8NYdVnG7FmYxEw.png" alt="Minimalist Nuxt Authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by&lt;/em&gt; &lt;a href="https://timiomoyeni.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Timi Omoyeni&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verifying user identity is crucial for protecting sensitive data. Authentication ensures users are who they claim to be, providing a vital defense against unauthorized access.&lt;/p&gt;

&lt;p&gt;The Nuxt team understands the importance of Authentication which is why with Nuxt 2, we had the &lt;a href="https://auth.nuxtjs.org/" rel="noopener noreferrer"&gt;Auth Module&lt;/a&gt;. The Auth module provided zero-boilerplate authentication for Nuxt 2 using a configurable authentication scheme (cookie, local, etc) or any of its supported providers(Auth0, Facebook, etc). &lt;/p&gt;

&lt;p&gt;Since the introduction of Nuxt 3, there have been 3rd party plugins like the &lt;a href="https://github.com/sidebase/nuxt-auth" rel="noopener noreferrer"&gt;Sidebase Nuxt Auth&lt;/a&gt;, &lt;a href="https://github.com/Hebilicious/authjs-nuxt" rel="noopener noreferrer"&gt;Authjs Nuxt&lt;/a&gt; based on Auth.js that have made the authentication process easier for developers. &lt;/p&gt;

&lt;p&gt;In this article, we will look into the Nuxt Auth Utils module, its features, and how to get started. The complete code for this article can be found on &lt;a href="https://github.com/Timibadass/nuxt-auth-demo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Nuxt Auth utils is a minimalist authentication module for Nuxt exposing Vue composables and server utils. While there are other modules and plugins with more features for authentication, Nuxt Auth Utils is great for implementing authentication on your own as it provides an opportunity to learn.&lt;/p&gt;

&lt;p&gt;With Nuxt Auth Utils, you automatically get access to data like &lt;code&gt;loggedIn&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, and &lt;code&gt;session&lt;/code&gt;, and the &lt;code&gt;clear&lt;/code&gt; method available in the &lt;code&gt;useUserSession()&lt;/code&gt; composable. These properties return updated information about the current user’s session and can be accessed anywhere in your app. &lt;/p&gt;

&lt;p&gt;This is similar to how we use the &lt;code&gt;$auth&lt;/code&gt; property to access user information in &lt;a href="https://auth.nuxtjs.org/api/auth" rel="noopener noreferrer"&gt;Nuxt 2&lt;/a&gt;, the difference now is that Nuxt 3 takes advantage of Vue Composables introduced in Vue 3. &lt;/p&gt;

&lt;p&gt;Creating a personalized profile header with the current user’s name can be done like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"loggedIn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        Hi
        {{ user?.username }}
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Guest&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clear&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using server utils, Nuxt auth utils automatically imports some helpers into the &lt;code&gt;server/&lt;/code&gt; directory that helps with session management. They include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;setUserSession&lt;/code&gt;: This function accepts two arguments, &lt;code&gt;event&lt;/code&gt;, and an object. Inside this object, we are expected to pass a &lt;code&gt;user&lt;/code&gt; object along with any extra information we want to save with the session.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replaceUserSession&lt;/code&gt;: This function behaves similarly to the &lt;code&gt;setUserSession&lt;/code&gt; but as the name implies, it replaces the current session data with the new user data that is passed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getUserSession&lt;/code&gt;: This function returns information about the current user session. This information is the same as the object passed to &lt;code&gt;setUserSession&lt;/code&gt; and &lt;code&gt;replaceUserSession&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clearUserSession&lt;/code&gt;: This function is used to clear a user’s session.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requireUserSession&lt;/code&gt;: This function works like a middleware that checks for a user session and returns it if present otherwise it returns a &lt;code&gt;401&lt;/code&gt; error.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... user data&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;loggedInAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;// Any extra fields&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Replace a user session. Same behaviour as setUserSession, except it does not merge data with existing data&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;replaceUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Get the current user session&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Clear the current user session&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;clearUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Require a user session (send back 401 if no `user` key in session)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requireUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this module requires the Nuxt server to be running as it uses the server API route hence, it cannot run with &lt;code&gt;nuxt generate&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nuxt Auth Utils Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hybrid Rendering
&lt;/h3&gt;

&lt;p&gt;Hybrid rendering allows different caching rules per route using Route Rules and decides how the server should respond to a new request on a given URL. &lt;/p&gt;

&lt;p&gt;When used with Nuxt Auth Utils, it does not fetch the user session during prerendering. It waits until after hydration and fetches it on the client side. This is because user sessions are stored in a secure cookie that cannot be accessed during prerendering. &lt;/p&gt;

&lt;p&gt;As a solution to this, we use the &lt;code&gt;&amp;lt;[AuthState](https://github.com/Atinux/nuxt-auth-utils/blob/c8b02d0b84a53ab4dd41f1808d9365d1c52c8366/src/runtime/app/components/AuthState.vue)&amp;gt;&lt;/code&gt; component to safely display auth-related data without worrying about the rendering mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AuthState&lt;/span&gt; &lt;span class="na"&gt;v-slot=&lt;/span&gt;&lt;span class="s"&gt;"{ loggedIn, clear }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"loggedIn"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"clear"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtLink&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/NuxtLink&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/AuthState&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component also offers support for a loading state that comes in handy while the user session is being fetched on the client side.&lt;/p&gt;

&lt;h3&gt;
  
  
  OAuth Providers
&lt;/h3&gt;

&lt;p&gt;Other than the native email/username and password authentication flow, Nuxt Auth Utils also &lt;a href="https://github.com/atinux/nuxt-auth-utils/tree/main?tab=readme-ov-file#supported-oauth-providers" rel="noopener noreferrer"&gt;offers support&lt;/a&gt; for authentication with third-party services using OAuth. It also comes with event handlers that are exposed from the &lt;code&gt;oauth&lt;/code&gt; global variable that is available in your server routes (&lt;code&gt;server/&lt;/code&gt; directory). Some of the supported providers include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;Auth0&lt;/li&gt;
&lt;li&gt;Facebook&lt;/li&gt;
&lt;li&gt;LinkedIn.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Extendable with Hooks
&lt;/h3&gt;

&lt;p&gt;This module also provides hooks that let you perform extra actions when a session is fetched or updated. This can be achieved by using &lt;a href="https://github.com/Atinux/nuxt-auth-utils/blob/c8b02d0b84a53ab4dd41f1808d9365d1c52c8366/src/runtime/server/utils/session.ts#L8" rel="noopener noreferrer"&gt;&lt;code&gt;sessionHook.hook&lt;/code&gt;&lt;/a&gt; method, which accepts a hook string (’fetch’ or ‘clear’).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNitroPlugin&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Called when the session is fetched during SSR for the Vue composable (/api/_auth/session)&lt;/span&gt;
  &lt;span class="c1"&gt;// Or when we call useUserSession().fetch()&lt;/span&gt;
  &lt;span class="nx"&gt;sessionHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extend User Session by calling your database&lt;/span&gt;
    &lt;span class="c1"&gt;// or&lt;/span&gt;
    &lt;span class="c1"&gt;// throw createError({ ... }) if session is invalid for example&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Called when we call useServerSession().clear() or clearUserSession(event)&lt;/span&gt;
  &lt;span class="nx"&gt;sessionHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Log that user logged out&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is available anywhere in the &lt;code&gt;server/&lt;/code&gt; directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with Nuxt Auth Utils
&lt;/h2&gt;

&lt;p&gt;To fully understand how Nuxt Auth Utils work, we will build a simple Nuxt app that uses an SQL server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;First, we create our Nuxt project using the npx command;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;nuxi&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt; &lt;span class="nx"&gt;nuxt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the auth utils in your Nuxt project, you need to install it using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;nuxi&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;utils&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Once this installation is completed, our &lt;code&gt;nuxt.config.ts&lt;/code&gt; file gets updated with the following lines of code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📄 nuxt.config.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://nuxt.com/docs/api/configuration/nuxt-config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nuxt-auth-utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// added automatically&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;nuxt-auth-utils&lt;/code&gt; is added to the &lt;code&gt;modules&lt;/code&gt; array so our application can start up our application with the configuration for the module. The next thing we need to do is to add  &lt;strong&gt;&lt;code&gt;NUXT_SESSION_PASSWORD&lt;/code&gt;&lt;/strong&gt; to our &lt;code&gt;.env&lt;/code&gt; file. This step is optional in development as the plugin automatically generates one if it isn’t set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building The Server
&lt;/h3&gt;

&lt;p&gt;Now that the configuration is complete, we will build our server. For this, we will use a simple SQLite server that requires &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; for both signup and login. &lt;/p&gt;

&lt;p&gt;The first thing we need to do is create a &lt;code&gt;server/&lt;/code&gt; directory in the root folder of our project. This directory will contain folders for both our database configuration and authentication files. &lt;/p&gt;

&lt;p&gt;Here’s what our  project structure will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;nuxt&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;demo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;signup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Before we can set up our database, we need to install the following packages in our project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;sqlite&lt;/code&gt;: This is the SQLite library for Node.js, which allows us to interact with SQLite databases.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlite3&lt;/code&gt;: This is the SQLite3 database driver for Node.js, used by the &lt;code&gt;sqlite&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bcrypt&lt;/code&gt;: This is a library to help us hash passwords, ensuring that user passwords are stored securely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can install these packages using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;sqlite&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing, we create a &lt;code&gt;database.js&lt;/code&gt; file and add the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 server/db/database.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./database.sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE,
        password TEXT
      )
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Database initialized successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to initialize database:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file establishes and initializes the SQLite database, makes sure the required tables are there, and offers a connection method. &lt;/p&gt;

&lt;p&gt;Firstly, we import &lt;code&gt;open&lt;/code&gt; function which is used to open and connect to the db, and &lt;code&gt;sqlite3&lt;/code&gt;, which is the SQLite database driver. We export &lt;code&gt;initDb&lt;/code&gt; function, which will open and connect to the db. &lt;/p&gt;

&lt;p&gt;In this function, we call the &lt;code&gt;open&lt;/code&gt; function and pass an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./database.sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This object contains &lt;code&gt;filename&lt;/code&gt;, which accepts the file path to our database file. &lt;/p&gt;

&lt;p&gt;This file is automatically created the first time &lt;code&gt;initDb&lt;/code&gt; is run and it uses the file path passed to this field to create this value. This means  &lt;code&gt;“./database.sqlite”&lt;/code&gt; will be created in the root folder of our project while &lt;code&gt;"./server/db/database.sqlite"&lt;/code&gt; will be created inside the &lt;code&gt;db/&lt;/code&gt; directory. &lt;/p&gt;

&lt;p&gt;We also have a &lt;code&gt;driver&lt;/code&gt; property that specifies which database to use (&lt;code&gt;sqlite3.database&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;open&lt;/code&gt; function resolves a promise, so we wrap it in a try/catch block. If the connection is successful, we execute an SQL command &lt;code&gt;db.exec&lt;/code&gt;, which creates a &lt;code&gt;users&lt;/code&gt; table if it does not exist in the db with each row having columns for a unique &lt;code&gt;id&lt;/code&gt;, and &lt;code&gt;username&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Still, if it fails, we throw an error that says &lt;code&gt;Failed to initialize database&lt;/code&gt; with the specific error. &lt;/p&gt;

&lt;p&gt;Finally, we export this &lt;code&gt;initDb&lt;/code&gt; function to reference our db from our auth files.&lt;/p&gt;




&lt;p&gt;After setting up our database, we will create an &lt;code&gt;api&lt;/code&gt; folder inside the &lt;code&gt;server/&lt;/code&gt; directory. Within this folder, we will create an &lt;code&gt;auth&lt;/code&gt; folder, which will contain both the &lt;code&gt;signup.js&lt;/code&gt; and &lt;code&gt;login.js&lt;/code&gt; files. &lt;/p&gt;

&lt;p&gt;Let us start with &lt;code&gt;signup.js:&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 server/api/auth/signup.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcrypt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initDb&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../db/database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Retrieve request body&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request body is empty or undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username and password are required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;initDb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Initialize database connection&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedPassword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hash password&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Insert user data into database&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO users (username, password) VALUES (?, ?)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;hashedPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loggedInAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error creating user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username already exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error handling signup request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to process request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use the &lt;a href="https://nuxt.com/docs/guide/directory-structure/server" rel="noopener noreferrer"&gt;&lt;code&gt;defineEventHandler&lt;/code&gt;&lt;/a&gt; function to set up our signup API so Nuxt recognizes our API and makes it available under &lt;code&gt;/api/auth/signup&lt;/code&gt;. In this function, we can access the body of this request (i.e &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;) by passing &lt;code&gt;event&lt;/code&gt; to &lt;a href="https://nuxt.com/docs/guide/directory-structure/server#body-handling" rel="noopener noreferrer"&gt;&lt;code&gt;readBody&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This function resolves a promise, so we must handle it within a &lt;code&gt;try/catch&lt;/code&gt; block to properly manage potential errors.&lt;/p&gt;

&lt;p&gt;On success, we get the request's body and destructure it to extract &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;. If there’s an error, we use &lt;a href="https://nuxt.com/docs/guide/directory-structure/server#error-handling" rel="noopener noreferrer"&gt;&lt;code&gt;createError&lt;/code&gt;&lt;/a&gt; for error handling. This ensures that any issues during the process are captured and reported appropriately.&lt;/p&gt;

&lt;p&gt;Before we can proceed with storing this user data in the database, we need to hash the user’s password. We use the &lt;a href="https://github.com/dcodeIO/bcrypt.js?tab=readme-ov-file#hashs-salt-callback-progresscallback" rel="noopener noreferrer"&gt;&lt;code&gt;bcrypt.hash&lt;/code&gt;&lt;/a&gt; method, which accepts two arguments: &lt;code&gt;password&lt;/code&gt; and &lt;code&gt;salt&lt;/code&gt;. The &lt;code&gt;password&lt;/code&gt; is the user’s plain text password, and &lt;code&gt;10&lt;/code&gt; is the salt length, determining the complexity of the hashing process.&lt;/p&gt;

&lt;p&gt;If the provided data is valid, the signup process succeeds, and we fetch the newly created user information using &lt;code&gt;db.get&lt;/code&gt;. This information is then passed to &lt;code&gt;setUserSession&lt;/code&gt; with a &lt;code&gt;loggedInAt&lt;/code&gt; property to store the time.&lt;/p&gt;

&lt;p&gt;At this point, our signup endpoint is ready and we can test it using either the &lt;a href="https://www.vuemastery.com/blog/exploring-the-nuxt-3-devtools" rel="noopener noreferrer"&gt;Nuxt DevTools&lt;/a&gt; or by creating a &lt;code&gt;signup.vue&lt;/code&gt; file in the &lt;code&gt;pages/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffirebasestorage.googleapis.com%2Fv0%2Fb%2Fvue-mastery.appspot.com%2Fo%2Fflamelink%252Fmedia%252F1.1720806818251.jpg%3Falt%3Dmedia%26token%3D698874e8-e4ae-4620-9358-862aaac3e263" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffirebasestorage.googleapis.com%2Fv0%2Fb%2Fvue-mastery.appspot.com%2Fo%2Fflamelink%252Fmedia%252F1.1720806818251.jpg%3Falt%3Dmedia%26token%3D698874e8-e4ae-4620-9358-862aaac3e263" alt="Testing endpoints in the server directory using Nuxt DevTools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When building out APIs in the &lt;code&gt;server/&lt;/code&gt; directory, Nuxt DevTools can automatically read the directory for valid APIs to display for easy testing. This is why we can see our &lt;code&gt;api&lt;/code&gt; folder listed here but not the &lt;code&gt;db&lt;/code&gt; folder. Using this approach, we can quickly test our API to ensure it works.&lt;/p&gt;

&lt;p&gt;In our app, we’re going to create a form component, &lt;code&gt;AuthForm.vue&lt;/code&gt; in our &lt;code&gt;components/&lt;/code&gt; directory which will look like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 components/AuthForm.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"auth__form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"auth__heading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{title}}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"auth__div"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form__label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Username&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form__input"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"auth__div"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form__label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form__input"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form__button"&lt;/span&gt; &lt;span class="na"&gt;:disabled=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;please wait...&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{title}}&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;defineProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineEmits&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.auth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;__form&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;__div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;__label&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;__input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;__button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#008065&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this component, we create a form that accepts &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;. When the form is submitted, the &lt;code&gt;submit&lt;/code&gt; function emits the value obtained from the form to the parent component using the &lt;code&gt;submit&lt;/code&gt; event. &lt;/p&gt;

&lt;p&gt;We also define props using the &lt;code&gt;defineProps&lt;/code&gt; method: &lt;code&gt;title&lt;/code&gt;, and &lt;code&gt;loading&lt;/code&gt;. Since we plan to use this form for signup and login, we use the &lt;code&gt;title&lt;/code&gt; prop to make the form heading dynamic. We use the &lt;code&gt;loading&lt;/code&gt; prop to indicate a network request is in progress and to disable the button during this time.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;pages/&lt;/code&gt; directory, we create &lt;code&gt;signup.vue&lt;/code&gt; with the following code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 pages/signup.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"signup"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AuthForm&lt;/span&gt; &lt;span class="na"&gt;:loading=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"register"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Sign up"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"signup__text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Already registered?
      &lt;span class="nt"&gt;&amp;lt;nuxt-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: 'login' }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log in&lt;span class="nt"&gt;&amp;lt;/nuxt-link&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AuthForm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/AuthForm.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusMessage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.signup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;__text&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;underline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we import &lt;code&gt;AuthForm.vue&lt;/code&gt;, provide the necessary props, and assign the &lt;code&gt;register&lt;/code&gt; function to handle the &lt;code&gt;submit&lt;/code&gt; event. When this form is submitted, we call our signup method using &lt;a href="https://nuxt.com/docs/api/utils/dollarfetch" rel="noopener noreferrer"&gt;&lt;code&gt;$fetch&lt;/code&gt;&lt;/a&gt;, attaching the request body and method to the request. &lt;/p&gt;

&lt;p&gt;On success, we redirect the user to the &lt;code&gt;/dashboard&lt;/code&gt; route, which can contain information like username, ID, login time, etc. &lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;dashboard.vue&lt;/code&gt; file looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 pages/dashboard.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"loggedIn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        Hi
        {{ user?.username }}
      &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log out&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hi Guest, &lt;span class="nt"&gt;&amp;lt;nuxt-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: 'login' }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/nuxt-link&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use the Nuxt Auth Utils composable to fetch log-in status (&lt;code&gt;loggedIn&lt;/code&gt;), user information (&lt;code&gt;user&lt;/code&gt;), the &lt;code&gt;fetch&lt;/code&gt; function that fetches the updated user information, and logout function (&lt;code&gt;clear&lt;/code&gt;). With this data available, we can display the username of the currently authenticated user.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;login.js&lt;/code&gt; API looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 server/api/auth/login.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcrypt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initDb&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../db/database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Retrieve request body&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request body is empty or undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request body is empty or undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username or password missing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username and password are required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;initDb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Initialize database connection&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE username = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// For security reasons, do not specify if username or password is incorrect&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid username or password for user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid username or password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setUserSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loggedInAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error handling login request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to process request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This file is similar to the &lt;code&gt;signup.js&lt;/code&gt; file as we use &lt;code&gt;defineEventHandler&lt;/code&gt; to declare our login function. After verifying the body of the login request contains both &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;, we initialize a connection to the database using our imported &lt;code&gt;initDb&lt;/code&gt; function. &lt;/p&gt;

&lt;p&gt;Upon a successful connection, we query the database for a matching username using the &lt;code&gt;.get("SELECT * FROM users WHERE username = ?",[username])&lt;/code&gt; where &lt;code&gt;username&lt;/code&gt; is the provided username in the body of the request. We also compare the provided password with the password in the database (if the user exists) using the &lt;code&gt;bcrypt.compare&lt;/code&gt; method and return an error if the passwords do not match. &lt;/p&gt;

&lt;p&gt;If the user exists in the database, we set the user session by calling &lt;code&gt;setUserSession&lt;/code&gt; and also pass &lt;code&gt;user&lt;/code&gt; and &lt;code&gt;loggedInAt&lt;/code&gt; to our session data. &lt;/p&gt;

&lt;p&gt;Finally, we use &lt;code&gt;createError&lt;/code&gt; to handle all the possible errors that may occur from this request and attach the appropriate status code and explanatory messages.&lt;/p&gt;

&lt;p&gt;To test our login function, we create a &lt;code&gt;login.vue&lt;/code&gt; in the &lt;code&gt;pages/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📂 pages/login.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;AuthForm&lt;/span&gt; &lt;span class="na"&gt;:loading=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"login"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Sign in"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"login__text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      New here?
      &lt;span class="nt"&gt;&amp;lt;nuxt-link&lt;/span&gt; &lt;span class="na"&gt;:to=&lt;/span&gt;&lt;span class="s"&gt;"{ name: 'signup' }"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign up&lt;span class="nt"&gt;&amp;lt;/nuxt-link&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AuthForm&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/AuthForm.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusMessage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"scss"&lt;/span&gt; &lt;span class="na"&gt;scoped&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.login&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;&amp;amp;__text&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;underline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we use the &lt;code&gt;AuthForm&lt;/code&gt; component to collect the user details and pass them to the &lt;code&gt;login&lt;/code&gt; function. In this function, we call our login endpoint &lt;code&gt;/api/auth/login&lt;/code&gt; and pass the payload which we get from the &lt;code&gt;AuthForm&lt;/code&gt; component. &lt;/p&gt;

&lt;p&gt;On a successful login, we redirect the user to the &lt;code&gt;/dashboard&lt;/code&gt; page by calling &lt;code&gt;router.push({name: 'Dashboard'})&lt;/code&gt; where &lt;code&gt;Dashboard&lt;/code&gt; is the route name which we show the username and a log out button.&lt;/p&gt;

&lt;p&gt;In this tutorial, we set up a basic session and user data implementation. However, our implementation can be further extended to use a stateful setup where a random session token is generated and stored in the user table, which can be used to verify a session’s validity. To see how this is done, be on the lookout for the Nuxt Authentication course.&lt;/p&gt;




&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;In this article, we have covered creating a server in Nuxt with SQL and how to use Nuxt Auth Utils to manage user sessions. We explored the features of Nuxt Auth Utils and highlighted their usefulness. &lt;/p&gt;

&lt;p&gt;With Nuxt Auth Utils, managing user sessions becomes seamless, secure, and efficient, ensuring your application provides a reliable and secure authentication experience and &lt;a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery" rel="noopener noreferrer"&gt;CSRF protection&lt;/a&gt;. To further your learning, keep an eye out for our upcoming Nuxt Authentication course here on Vue Mastery!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/minimalist-nuxt-authentication" rel="noopener noreferrer"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 12, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>vue</category>
      <category>nuxt</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Upgrading to Nuxt 4</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Mon, 24 Jun 2024 10:48:49 +0000</pubDate>
      <link>https://forem.com/vuemastery/upgrading-to-nuxt-4-2gb0</link>
      <guid>https://forem.com/vuemastery/upgrading-to-nuxt-4-2gb0</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bbkg42Wg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AQrd_wR2gjCfwI0RX-RQHSg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bbkg42Wg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AQrd_wR2gjCfwI0RX-RQHSg.jpeg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nuxt v4 is coming out soon. This version of Nuxt is predominantly about performance upgrades and API consistency. Although there are no ground-breaking user-facing changes, we'll still go through them one by one in this tutorial to make sure your Nuxt v3 app can still run on v4.&lt;/p&gt;




&lt;p&gt;At the time of this writing, nuxt v4 has not been released yet. But you can still try it out using v3.12.&lt;/p&gt;

&lt;p&gt;First, upgrade the Nuxt version in your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx nuxi@latest upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will upgrade your project to the latest Nuxt version.&lt;/p&gt;

&lt;p&gt;With v3.12, you have to set the &lt;code&gt;compatibilityVersion&lt;/code&gt; option to &lt;code&gt;4&lt;/code&gt;:&lt;br&gt;
&lt;strong&gt;nuxt.config.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;future&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compatibilityVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's talk about the changes you should be expecting in Nuxt 4.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nuxt 4 Folder Structure
&lt;/h2&gt;

&lt;p&gt;The most obvious change in Nuxt v4 is the new folder structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vh1n2CEp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F1.1719254486707.jpg%3Falt%3Dmedia%26token%3D5e849d7f-a737-47ee-aa1b-2518a3de365d" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vh1n2CEp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F1.1719254486707.jpg%3Falt%3Dmedia%26token%3D5e849d7f-a737-47ee-aa1b-2518a3de365d" alt="folder_structure.png" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have to put the client-side code in the &lt;strong&gt;app&lt;/strong&gt; folder instead of the root folder. But the server folder can stay where it is.&lt;/p&gt;

&lt;p&gt;This change would require you to move some of your files around, but this change is optional. If you don't do it, Nuxt can still detect it and use the old way.&lt;/p&gt;

&lt;p&gt;This is a performance upgrade because the file watchers don't have to watch all the files in the root folder. Secondly, this is also a DX upgrade because IDEs can provide better support with client code and server code separated in their own folders. For instance, server code is usually running in a Node.js environment, and the client code is running in a browser environment. Separating the two different types of code means that the IDE can be configured for each folder separately.&lt;/p&gt;




&lt;h2&gt;
  
  
  useAsyncData &amp;amp; useFetch
&lt;/h2&gt;

&lt;p&gt;There are a handful of miscellaneous changes with &lt;code&gt;useAsyncData&lt;/code&gt; and &lt;code&gt;useFetch&lt;/code&gt; that you need to be mindful of.&lt;/p&gt;

&lt;p&gt;First of all, the data fetched using &lt;code&gt;useAsyncData&lt;/code&gt; and &lt;code&gt;useFetch&lt;/code&gt; will be cached and made available to other pre-rendered pages without refetching. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--neBcSJq---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F2.1719254486708.jpg%3Falt%3Dmedia%26token%3Da6f8eeaa-ac3b-492d-adb4-3535d20aea4b" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--neBcSJq---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F2.1719254486708.jpg%3Falt%3Dmedia%26token%3Da6f8eeaa-ac3b-492d-adb4-3535d20aea4b" alt="cached_shared.png" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This data-sharing feature has been experimental, but in Nuxt 4, it’s a real feature.&lt;/p&gt;




&lt;p&gt;The data ref that gets returned will now be a shallow ref:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JwS8p0tY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F3.1719254491623.jpg%3Falt%3Dmedia%26token%3D34ce9561-8585-4f6f-9b8d-12254f3f3f77" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JwS8p0tY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F3.1719254491623.jpg%3Falt%3Dmedia%26token%3D34ce9561-8585-4f6f-9b8d-12254f3f3f77" alt="shallowref.png" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Shallow ref will only be reactive if the &lt;code&gt;.value&lt;/code&gt; itself is reassigned.)&lt;/p&gt;




&lt;p&gt;Both of these composables also return a &lt;code&gt;refresh&lt;/code&gt; function that you can call to refetch the data. And this &lt;code&gt;refresh&lt;/code&gt; function can be configured with a &lt;code&gt;dedupe&lt;/code&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CKFfS8ak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F4.1719254495785.jpg%3Falt%3Dmedia%26token%3Dfe5b595c-3f5c-44cc-9a90-2976f98f3418" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CKFfS8ak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F4.1719254495785.jpg%3Falt%3Dmedia%26token%3Dfe5b595c-3f5c-44cc-9a90-2976f98f3418" alt="refresh_dedupe.png" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of using &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt;, now you have to use &lt;code&gt;cancel&lt;/code&gt; or &lt;code&gt;defer&lt;/code&gt; to set the &lt;code&gt;dedupe&lt;/code&gt; option. Cancel means cancelling the duplicated request (the new one), and defer means wait for the existing one to finish before executing the new one.&lt;/p&gt;




&lt;p&gt;When &lt;code&gt;useAsyncData&lt;/code&gt; is configured with a default value, &lt;code&gt;refreshNuxtData&lt;/code&gt; will reset the data back to that default value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-bY-RCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F5.1719254499632.jpg%3Falt%3Dmedia%26token%3D44f96b50-ae89-4f1d-a7b8-303f2d2eff1c" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-bY-RCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F5.1719254499632.jpg%3Falt%3Dmedia%26token%3D44f96b50-ae89-4f1d-a7b8-303f2d2eff1c" alt="useAsyncData_clearNuxtData.png" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Previously, this would reset the &lt;code&gt;comments&lt;/code&gt; to &lt;code&gt;undefined&lt;/code&gt;. And since &lt;code&gt;useFetch&lt;/code&gt; couldn’t be configured with a default value, this doesn’t affect &lt;code&gt;useFetch&lt;/code&gt;.)&lt;/p&gt;




&lt;p&gt;On a related note, if you didn’t set a default value, it will now default to &lt;code&gt;undefined&lt;/code&gt;. This applies to both &lt;code&gt;useAsyncData&lt;/code&gt; and &lt;code&gt;useFetch&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F-mscIYe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F6.1719254499633.jpg%3Falt%3Dmedia%26token%3Ddfe19489-9034-457f-8dce-362d7310f54e" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F-mscIYe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%252Fmedia%252F6.1719254499633.jpg%3Falt%3Dmedia%26token%3Ddfe19489-9034-457f-8dce-362d7310f54e" alt="undefined_by_default.png" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Previously, it defaults to &lt;code&gt;null&lt;/code&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Nuxt 4 Changes
&lt;/h2&gt;

&lt;p&gt;Finally, there are changes that don't affect most projects so you probably don't need to worry about them. But I include them here for completeness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now Nuxt scans the &lt;strong&gt;index.js&lt;/strong&gt; files in the child folders inside &lt;strong&gt;/middleware&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Now the &lt;code&gt;builder:watch&lt;/code&gt; hook emits an absolute path, instead of a relative path.&lt;/li&gt;
&lt;li&gt;Some template-specific code generation utils have been removed, and &lt;strong&gt;.ejs&lt;/strong&gt; file compilation is removed, too.&lt;/li&gt;
&lt;li&gt;Some experimental features have also been removed, so their corresponding config options have been removed too: &lt;code&gt;treeshakeClientOnly&lt;/code&gt;, &lt;code&gt;configSchema&lt;/code&gt;, &lt;code&gt;polyfillVueUseHead&lt;/code&gt;, &lt;code&gt;respectNoSSRHeader&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where to go next?
&lt;/h2&gt;

&lt;p&gt;From a framework user standpoint, Nuxt 3 and Nuxt 4 are basically the same Nuxt. You can think of Nuxt 4 as a more fleshed out version of Nuxt 3. This is great because you can still use most of the Nuxt 3 learning materials currently available. &lt;/p&gt;

&lt;p&gt;If you enjoy visually illustrated content like this tutorial, you should check out the &lt;a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro" rel="noopener noreferrer"&gt;Real World Nuxt&lt;/a&gt; course and the &lt;a href="https://www.vuemastery.com/courses/nuxt-api-routes/api-routes-introduction" rel="noopener noreferrer"&gt;Nuxt API Routes&lt;/a&gt; course here on &lt;a href="https://www.vuemastery.com" rel="noopener noreferrer"&gt;VueMastery.com&lt;/a&gt;.&lt;br&gt;
&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/upgrading-to-nuxt-4"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on June 24, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>nuxt</category>
      <category>javascript</category>
      <category>vue</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Nuxt’s Server-Only Components should be on your radar</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Mon, 25 Mar 2024 10:48:37 +0000</pubDate>
      <link>https://forem.com/vuemastery/nuxts-server-only-components-should-be-on-your-radar-2pb</link>
      <guid>https://forem.com/vuemastery/nuxts-server-only-components-should-be-on-your-radar-2pb</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HcH5KD2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AwBNz9QuED0RkT18d7anoBA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HcH5KD2F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AwBNz9QuED0RkT18d7anoBA.jpeg" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Andy Li&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Have you heard of &lt;em&gt;React Server Component&lt;/em&gt;? It’s a newer type of component rendering that has been making a wave in the React community in the past year.&lt;/p&gt;

&lt;p&gt;The significant aspect of React Server Component is its server-only rendering. This means it doesn’t necessitate client-side rendering, eliminating the need for hydration. Hydration refers to the process of rendering JavaScript components on the client side. As it involves loading and executing code, hydration can significantly impact the first-load performance of a Server Side Rendered (SSR) webpage.&lt;/p&gt;

&lt;p&gt;React Server Component is all about reducing the need for hydration.&lt;/p&gt;

&lt;p&gt;Nuxt.js now has something similar called &lt;strong&gt;Server-Only Component&lt;/strong&gt; (or just &lt;strong&gt;Server Component&lt;/strong&gt; ). As of Nuxt v3.11, it’s still an experimental feature. But we can try it out and see what it’s all about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Only Rendering
&lt;/h3&gt;

&lt;p&gt;As it sounds, “ &lt;strong&gt;Server-only Component&lt;/strong&gt; ” means it only runs and renders on the server, never on the client. It will only send the rendered HTML to the client. The source code of that component is not needed on the client side.&lt;/p&gt;

&lt;p&gt;To use this feature, there’s no need to change your code. Just add the extension  &lt;strong&gt;.server&lt;/strong&gt; before the  &lt;strong&gt;.vue&lt;/strong&gt; extension.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXxBjBzK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A4DxJAEQFBq2euuML" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXxBjBzK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A4DxJAEQFBq2euuML" alt="" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(you can clone this sample app &lt;a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro"&gt;here&lt;/a&gt;, it’s from my course &lt;a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro"&gt;Real World Nuxt 3&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;You can add the extension to any component or page component that you want to render only on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros and Cons
&lt;/h3&gt;

&lt;p&gt;The good thing is that the page will now load faster without an additional hydration step on the front end.&lt;/p&gt;

&lt;p&gt;Here’s a performance comparison on loading the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jxua1cZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Anbt15Rcgg6PQvir-" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jxua1cZL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Anbt15Rcgg6PQvir-" alt="" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---iqCwfgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AgJJw7cPnGA3ILmTF" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---iqCwfgA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AgJJw7cPnGA3ILmTF" alt="" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(ignore &lt;strong&gt;Idle&lt;/strong&gt; and &lt;strong&gt;Total&lt;/strong&gt; , they are not relevant to the page’s performance)&lt;/p&gt;

&lt;p&gt;Look at the &lt;strong&gt;Scripting&lt;/strong&gt; part (the yellow part), that’s where the hydration takes place. Since there’s no hydration with server-only rendering, there’s less &lt;strong&gt;Scripting&lt;/strong&gt; work needed to be done while loading the page.&lt;/p&gt;

&lt;p&gt;Additionally, the source code of the page component will only run on the server, meaning all related dependencies will also remain there. This includes any sensitive logic, like fetch calls to an API server that should be kept private. As long as these are not used in other regular components, they too will stay on the server.&lt;/p&gt;

&lt;p&gt;But the tradeoff is that a server-only component can’t be interactive because they are no longer Vue components on the client side. They are just a bunch of HTML at that point.&lt;/p&gt;

&lt;p&gt;In practice, we would have to mix and match different types of components to get the desired results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mixing Server-Only Components and SSR
&lt;/h3&gt;

&lt;p&gt;Once &lt;strong&gt;server-only components&lt;/strong&gt; are no longer in the experimental stage, an intuitive method should be available to mark a nested component as interactive, tentatively via the nuxt-client attribute. This would allow you to render a page as server-only, while also having the ability to nest interactive components within the page, rendered with classic SSR. This is a &lt;em&gt;top-down&lt;/em&gt; approach.&lt;/p&gt;

&lt;p&gt;Alternatively, the page can be rendered using traditional Server Side Rendering (SSR), where you can selectively choose which nested components to render in server-only mode. Simply add the  &lt;strong&gt;.server&lt;/strong&gt; extension to the components you want to render as server-only. This is the &lt;em&gt;bottom-up&lt;/em&gt; approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bpkdl9RM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2ASVr7POOidwZWgFxS" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bpkdl9RM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2ASVr7POOidwZWgFxS" alt="" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The top-down approach is great for pages with mostly static content, but where there’s a bit of interactive UI.&lt;/p&gt;

&lt;p&gt;The bottom-up approach is more suitable when the page frame is highly interactive, but contains a large section of static content. For instance, the main body of a blog article usually displays a significant amount of static content.&lt;/p&gt;

&lt;p&gt;As of version 3.11.1, this feature is still under development, so the final developer experience (DX) remains uncertain.&lt;/p&gt;

&lt;p&gt;However, the server-only component feature is built on the  component. The DX of using  is more defined, so we’ll discuss that next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuxt Island
&lt;/h3&gt;

&lt;p&gt;Instead of making an entire component server-only, we can render a specific component in the template server-only by using the  component.&lt;/p&gt;

&lt;p&gt;For example, with this code (from the &lt;a href="https://github.com/Code-Pop/Real-World-Nuxt-3/tree/L6-end"&gt;sample project&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/pages/posts/[post].vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
        {{ post.title }}
        &lt;span class="nt"&gt;&amp;lt;CategoryLink&lt;/span&gt; &lt;span class="na"&gt;:category=&lt;/span&gt;&lt;span class="s"&gt;"post.category"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;RenderMarkdown&lt;/span&gt; &lt;span class="na"&gt;:source=&lt;/span&gt;&lt;span class="s"&gt;"post.content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is rendering some markdown content using a component called RenderMarkdown.&lt;/p&gt;

&lt;p&gt;We can render it in the server-only mode through :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/pages/posts/[post].vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
        {{ post.title }}
        &lt;span class="nt"&gt;&amp;lt;CategoryLink&lt;/span&gt; &lt;span class="na"&gt;:category=&lt;/span&gt;&lt;span class="s"&gt;"post.category"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;NuxtIsland&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"RenderMarkdown"&lt;/span&gt;
        &lt;span class="na"&gt;:props=&lt;/span&gt;&lt;span class="s"&gt;"{ source: post.content }"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RenderMarkdown has to have the  &lt;strong&gt;.island.vue&lt;/strong&gt; extension for this to work.&lt;/p&gt;

&lt;p&gt;This approach is more flexible because the rendering of a component is more related to the context in which that component is used rather than the component itself.&lt;/p&gt;

&lt;p&gt;In this example, the prop post.content doesn’t get changed like a state does, so rendering it in server-only would improve performance without sacrificing the functionality of the page&lt;/p&gt;

&lt;p&gt;However, there might be another scenario where we use RenderMarkdown and the prop passed to it is supposed to be reactive. In such cases, rendering RenderMarkdown server-only may not be optimal. The reactivity of the prop would trigger the server-only component to render again on the server over the network, and have its HTML sent to the frontend, also over the network. This could actually result in degraded performance.&lt;/p&gt;

&lt;p&gt;So with , you have the freedom to choose how a component should be rendered when you’re using it, not when you’re creating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking Ahead
&lt;/h3&gt;

&lt;p&gt;The Nuxt team has mentioned that v3.11 is likely the last minor update before the next major version. Hopefully, we’ll see &lt;strong&gt;server-only component&lt;/strong&gt; as a stable feature soon in Nuxt v4.&lt;/p&gt;

&lt;p&gt;Until then, you can still optimize the performance of your site by utilizing the various rendering modes currently available in Nuxt.js. You can check out the &lt;a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro"&gt;Real World Nuxt 3&lt;/a&gt; course on Vue Mastery to learn all about that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/nuxts-server-only-components-should-be-on-your-radar"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 25, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>nuxt</category>
      <category>frontend</category>
      <category>vue</category>
    </item>
    <item>
      <title>Effortless Forms w/ FormKit</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Fri, 22 Mar 2024 10:48:26 +0000</pubDate>
      <link>https://forem.com/vuemastery/effortless-forms-w-formkit-47nn</link>
      <guid>https://forem.com/vuemastery/effortless-forms-w-formkit-47nn</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZY0e-3Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AZ5A0SGz2-zq7kbFgjwU0_w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZY0e-3Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AZ5A0SGz2-zq7kbFgjwU0_w.jpeg" alt="Effortless Forms with FormKit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by&lt;/em&gt; &lt;a href="https://timiomoyeni.com/"&gt;&lt;em&gt;Timi Omoyeni&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When building a web app, one of the most common features developers will be building and managing is forms. They can be crucial to the success of a business, which is why it’s so important that your forms are intuitive for users to complete, well validated, and easy for developers to maintain.&lt;/p&gt;

&lt;p&gt;This is where a tool like &lt;a href="https://formkit.com/"&gt;FormKit&lt;/a&gt; comes in, which provides not just UI elements to deliver high quality forms, but offers built-in validation, and support for more advanced features like schema-driven forms and more.&lt;/p&gt;

&lt;p&gt;Join me as we explore how FormKit can save us time, energy and maintenance for use in our Vue.js projects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You can find all the code examples used in this tutorial on &lt;em&gt;[_GitHub&lt;/em&gt;](&lt;a href="https://github.com/Timibadass/formkit-app"&gt;https://github.com/Timibadass/formkit-app&lt;/a&gt;)&lt;/em&gt;._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Introduction to FormKit
&lt;/h3&gt;

&lt;p&gt;According to their official documentation, &lt;a href="https://formkit.com/getting-started/what-is-formkit"&gt;FormKit&lt;/a&gt; is a comprehensive form-building framework designed for Vue developers. It enables faster and more accessible authoring of high-quality production-ready forms, with improved DX, UX, and reduced code.&lt;/p&gt;

&lt;p&gt;Naturally, when building out forms in projects, regardless of the framework, it’s common to use libraries that assist with validation in addition to UI libraries for the required form components.&lt;/p&gt;

&lt;p&gt;From there, building out the form schema becomes your responsibility. Depending on the complexity, it can be a tedious task. FormKit is unique because it’s a one-stop shop that enables developers to efficiently build all of the necessary features without relying heavily on other libraries.&lt;/p&gt;

&lt;p&gt;FormKit’s underlying architecture exists to make the process of obtaining data from your form more straightforward, regardless of how complex the structure may seem.&lt;/p&gt;

&lt;p&gt;Here are some of the features the FormKit framework provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Form UI elements&lt;/strong&gt; : The  element accepts a type prop for the form element type you need. This component translates to the default HTML tag in the browser, while also providing additional features beyond the default elements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FSmhPPQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A7efLL2bXjYuOaV6V" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FSmhPPQl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A7efLL2bXjYuOaV6V" alt="" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Form Configuration:&lt;/strong&gt; FormKit provides additional attributes/props like validations, prefix-icon, help, and many more that further enhance the behavior of our form and also help provide a better UX for users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_u0UVj2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AxdbrHqm25T6JZQ1G" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_u0UVj2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AxdbrHqm25T6JZQ1G" alt="" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Form Schema&lt;/strong&gt; : FormKit also provides a powerful schema to store and generate forms. Under the hood, the FormKit element is powered by this schema, a JSON-serializable data format for storing DOM structures and implementations.&lt;/p&gt;

&lt;p&gt;FormKit has other exciting features, some of which include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Styling&lt;/li&gt;
&lt;li&gt;Accessibility&lt;/li&gt;
&lt;li&gt;Extensibility&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;FormKit is particularly useful in form building because it embodies all the necessary features needed when building out a form. This comes in handy when thinking about performance and the maintenance of your codebase because you can set up validation, a beautiful UI, and also generate forms using JSON schema using only FormKit. This reduces the number of libraries needed to get your project running, thereby reducing your application’s bundle size and ultimately improving your app’s performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started with Formkit
&lt;/h3&gt;

&lt;p&gt;To use FormKit in our project, we’ll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js v&lt;/strong&gt; 14.18.0, 16.12.0, or higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vue 3&lt;/strong&gt; or &lt;strong&gt;Nuxt 3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A terminal to run our commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Nuxt 2 or Vue 2 users looking for a library that offers the same benefits as FormKit, you can check out &lt;a href="https://vueformulate.com/"&gt;VueFormulate&lt;/a&gt;. VueFormulate is a predecessor to FormKit. It includes all the features and supports older versions of Vue and Nuxt.&lt;/p&gt;

&lt;p&gt;To install FormKit in a new project, you can use any of the package managers like npm or yarn using the following commands:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm&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;// Nuxt 3
npm install @formkit/nuxt

// Vue 3
npm install @formkit/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;yarn&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;// Nuxt 3
yarn add @formkit/nuxt

// Vue 3
yarn add @formkit/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FormKit also offers an installation command for setting up a project from scratch with FormKit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx formkit@lates create-app
// or 
npx formkit@latest create-app //latest formkit version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command is followed by a series of prompts that guide you on how to set up your new project.&lt;/p&gt;

&lt;p&gt;For this tutorial, we are going to create a Vue 3 project using the npx formkit@latest create-app command and follow the setup prompt that follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx formkit@latest create-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this setup is complete, we can install FormKit in our app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vqRaZ7yW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AlaD5TrBoufS2pbVl" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vqRaZ7yW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AlaD5TrBoufS2pbVl" alt="" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this installation is complete, along with the other instructions provided, when we visit the link our app is hosted on, we should see a default FormKit page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nfx3DJN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A6k6ZlV4mqecXC9Rq" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nfx3DJN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A6k6ZlV4mqecXC9Rq" alt="" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code powering this page can be found in App.vue and it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submitted! 🎉&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-white rounded-xl shadow-xl p-8 mx-auto my-16 max-w-[450px]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
      &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://pro.formkit.com/logo.svg"&lt;/span&gt;
      &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"FormKit Logo"&lt;/span&gt;
      &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"244"&lt;/span&gt;
      &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mx-auto mb-8 w-48"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;
      &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="err"&gt;="{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt; &lt;span class="err"&gt;}"&lt;/span&gt;
      &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
        &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Name"&lt;/span&gt;
        &lt;span class="na"&gt;help=&lt;/span&gt;&lt;span class="s"&gt;"What do people call you?"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"flavors"&lt;/span&gt;
        &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Favorite ice cream flavors"&lt;/span&gt;
        &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"{
          'vanilla': 'Vanilla',
          'chocolate': 'Chocolate',
          'strawberry': 'Strawberry',
          'mint-chocolate-chip': 'Mint Chocolate Chip',
          'rocky-road': 'Rocky Road',
          'cookie-dough': 'Cookie Dough',
          'pistachio': 'Pistachio',
        }"&lt;/span&gt;
        &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required|min:2"&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"agree"&lt;/span&gt;
        &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"I agree FormKit is the best form authoring framework."&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"font-mono text-sm p-4 bg-slate-100 mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ value }}&lt;span class="nt"&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we can see the FormKit component being used in four places, with the name prop accepting three different values:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;form: used for binding all values from all form elements as one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o56NkwKG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AIgCe8R8xeTcLCcXd" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o56NkwKG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AIgCe8R8xeTcLCcXd" alt="" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;checkbox.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also have a formkit.theme.mjs, which is shipped by default when you set up your project using the &lt;strong&gt;npx&lt;/strong&gt; command. This theme is known as Regenesis which, under the hood is a Tailwind theme that can be useful in styling your form components.&lt;/p&gt;

&lt;p&gt;We are going to learn about all the possible values that the FormKit element can accept and how we can make the best out of all the features FormKit has to offer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The FormKit Element
&lt;/h3&gt;

&lt;p&gt;Now that we have successfully set up our project to use FormKit, it is time to look at what the  component can do. FormKit ships with only this component, which is capable of serving all intended purposes related to building a complete form. This is possible because the component includes a few props that are used for accessibility and general form logic.&lt;/p&gt;

&lt;p&gt;Let’s take a look at some of these props.&lt;/p&gt;

&lt;h3&gt;
  
  
  FormKit type property
&lt;/h3&gt;

&lt;p&gt;The FormKit component accepts a type prop that is used to set the behavior of the component. This prop accepts a handful of values, most of which are native input element types (text, email, date, password) and are used to determine the type of input to render. By default, this prop has a value of text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we inspect our code in the browser, we can see what the  component translates to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b19_OjjG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A-oyoO1y8g1eZJQI1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b19_OjjG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A-oyoO1y8g1eZJQI1" alt="" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The type prop also accepts a type form that is used to group fields as well as make the process of validation much easier.&lt;/p&gt;

&lt;p&gt;Here’s an example of what that would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Car brand"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Number of seats"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"numberOfSeats"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FormKit name property
&lt;/h3&gt;

&lt;p&gt;The name property is used to bind input values to their corresponding property. If this prop is not set on your form, a default value of type + ‘_n’ is set where type is the input type passed to the component and n is the number of input elements you have in your form.&lt;/p&gt;

&lt;p&gt;For example, if we do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It translates to the following in the DOM:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an input element of type ”text”.&lt;/li&gt;
&lt;li&gt;Give it a name of “address”.&lt;/li&gt;
&lt;li&gt;Bind every value in this form to the property address in our imaginary form object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The name prop comes in handy when you group your input elements using the type value of form. This automatically binds the value in all the input fields in your form to their respective name values without the need for v-model.&lt;/p&gt;

&lt;p&gt;Other FormKit component props include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;label&lt;/li&gt;
&lt;li&gt;placeholder&lt;/li&gt;
&lt;li&gt;help&lt;/li&gt;
&lt;li&gt;plugins&lt;/li&gt;
&lt;li&gt;validation-visibility&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While we are already familiar with some of these properties, we’ll explore the others and see them in action in a later section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a simple form with FormKit
&lt;/h3&gt;

&lt;p&gt;When building a login form using plain HTML, it usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we have a login form that consists of email and password fields, each with their respective labels, as well as a submit button.&lt;/p&gt;

&lt;p&gt;When viewed in the browser, we’d get a form that looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--miTSlrD8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AnQFRbJRkTAmXuEdR" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--miTSlrD8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AnQFRbJRkTAmXuEdR" alt="" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we can recreate this form with fewer lines of code using FormKit.&lt;/p&gt;

&lt;p&gt;Let’s create a new component called &lt;strong&gt;LoginForm&lt;/strong&gt; , which will use three types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;form&lt;/strong&gt; : This type helps us group all the values in our input field under one form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;email&lt;/strong&gt; : We use this to inform FormKit that we expect an email address to be entered in the input. Thus, it should apply the default email validation to the given value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;password&lt;/strong&gt; : We also use this value to specify that we want a password input field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;LoginForm.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we check this out in the browser, we should see a simple login form being displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L4YT9UDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AjHEWqbxcx-O1HKEA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L4YT9UDC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AjHEWqbxcx-O1HKEA" alt="" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, we can see that a submit button is automatically added to this form. This is part of the benefits that come with wrapping our form inside a . This input type comes with several benefits like error handling, form submission, loading states, and more. We can change the text of this button using the submit-label prop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LoginForm.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we save this change and view it in the browser, we should see that our form button now has the text  &lt;strong&gt;“Login”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uMleQJIF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A1hlowMe3MK18ggI8" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uMleQJIF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A1hlowMe3MK18ggI8" alt="" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As discussed earlier, another benefit of grouping our form elements using the type form is being able to access the values of every field inside this form.&lt;/p&gt;

&lt;p&gt;To see this in action, we are going to modify our LoginForm.vue component.&lt;/p&gt;

&lt;p&gt;In this code snippet, we have added a @submit event with a logDetails function. This event is the same as the native submit event we are used to; it returns an argument that contains all the values of the inputs in the form.&lt;/p&gt;

&lt;p&gt;To demonstrate this, let’s log the values in our console to show that they are correct. With this approach, we do not need to use v-model in our input fields to get their values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"logDetails"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;//log values to the console&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logDetails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see the values of our form being logged after submitting the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sm5BX12E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Ahojdssp_uTzRAbcm" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sm5BX12E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Ahojdssp_uTzRAbcm" alt="" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the moment, we can submit the form without filling in the form, which is not how most production-ready forms work. To fix this, we are going to add a validation prop to both the email and password fields and set it to required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"logDetails"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, if we try to add and delete any property from either field, this is what we see in the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XJicQvr6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/937/0%2AfOlx2qtv6ub8pdKS" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XJicQvr6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/937/0%2AfOlx2qtv6ub8pdKS" alt="" width="800" height="854"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also adds another error when you attempt to submit the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r-eXhaJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/852/0%2ADYBiRTY1RYAh0P0X" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r-eXhaJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/852/0%2ADYBiRTY1RYAh0P0X" alt="" width="800" height="939"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this form, simply passing a required value for validation meets our current needs. As we proceed, we’ll explore additional validation options for more intricate forms.&lt;/p&gt;

&lt;h3&gt;
  
  
  FormKit Configuration
&lt;/h3&gt;

&lt;p&gt;As we continue building our forms, we may need further configurations, such as custom validation and data manipulation. Since FormKit aims to be the “form framework” for every project and JavaScript framework on the web, it includes everything you are likely to need to build a form. Let’s take a look at some of the available configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validations
&lt;/h3&gt;

&lt;p&gt;In the Vue.js ecosystem, libraries such as Vuelidate are available for form validation and validation messages. These tools simplify the complex task of validation, allowing us to concentrate on the UI and overall application logic.&lt;/p&gt;

&lt;p&gt;As we saw above, with FormKit we can achieve this task using the validation prop.&lt;/p&gt;

&lt;p&gt;In the code snippet below, we are using two types of inputs: select for “cars” and “numberOfSeats”, and number for “price”.&lt;/p&gt;

&lt;p&gt;To handle validation, we can make use of the validation prop which is attached to all fields. With FormKit, we can use multiple validation rules for one input field by separating the rules with a pipe (|) as used in our “Price” field: validation="required | min:10000 | max:50000"&lt;/p&gt;

&lt;p&gt;For this input, we make use of three validation rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;required: This field is required.&lt;/li&gt;
&lt;li&gt;min: The value entered for our price cannot be less than 10,000.&lt;/li&gt;
&lt;li&gt;max: The value entered for our price cannot exceed 50,000.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;FormKitExample.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"validateForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Car brand"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.car"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Number of seats"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"seats"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"numberOfSeats"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.numberOfSeats"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.price"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required | min:10000 | max:50000"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the full list of validation rules in FormKit &lt;a href="https://formkit.com/essentials/validation#available-rules"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to validation, FormKit provides a validation-messages prop that allows you to customize the error messages for your validations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.price"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required | min:10000 | max:50000"&lt;/span&gt; &lt;span class="na"&gt;:validation-messages=&lt;/span&gt;&lt;span class="s"&gt;"{
    max: ({ node: { value } }) =&amp;gt; `${value} is above the allowed price range`
   }"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we added the validation-messages prop which accepts an object of the validation rules we intend to customize. Each rule returns a function that takes the &lt;a href="https://formkit.com/essentials/architecture#node"&gt;node&lt;/a&gt; as an argument, this node has access to the form element (input, form, group) and the value for them (depending on the type). This allows us to make use of the value inside the input field in our custom message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IVBIVNma--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2ARSiswAyfz8fByziM" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IVBIVNma--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2ARSiswAyfz8fByziM" alt="" width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling with FormKit
&lt;/h3&gt;

&lt;p&gt;The use of input fields and form validations has become standard practice on the frontend, regardless of backend validation. Despite these precautions, submission errors can still occur due to various reasons such as server downtime or validation errors. With FormKit, we can display these errors to the user with any of the following methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using the errors prop on the  element.&lt;/li&gt;
&lt;li&gt;Displaying errors using node.setErrors()&lt;/li&gt;
&lt;li&gt;Using the FormKit Vue plugin method $formkit.setErrors()&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using the errors Prop in Formkit
&lt;/h3&gt;

&lt;p&gt;This approach necessitates the grouping of your form using the  component. This component has access to the @submit event and the values of all fields within your form.&lt;/p&gt;

&lt;p&gt;Consequently, we can utilize this event to decide when to display these errors to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"createUser"&lt;/span&gt; &lt;span class="na"&gt;:errors=&lt;/span&gt;&lt;span class="s"&gt;"errors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"fullName"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fullName"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
      &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Full Name"&lt;/span&gt;
      &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
      &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;
      &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt;
      &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt;
      &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Address"&lt;/span&gt;
      &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"textarea"&lt;/span&gt;
      &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something happened&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we have a form with three inputs, fullName, email, and address. We group this form using the  and add an errors prop. We pass an errors ref to this prop so we can set the errors dynamically after submitting the form. This is our way of mimicking a network request to the server that fails and returns errors that must be displayed to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Displaying errors using node.setErrors() in FormKit
&lt;/h3&gt;

&lt;p&gt;When a method is passed to the @submit event, it automatically receives value and node as arguments. The node provides us with all the information about the form while the value contains the values of all fields inside the form in an object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ITTsd9Eq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Aq1QcJY64Z7WpK8mk" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ITTsd9Eq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Aq1QcJY64Z7WpK8mk" alt="" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this approach, we can handle errors that are specific to both the input fields and the general form. We can achieve this by using the submit event on the form and the setErrors method.&lt;/p&gt;

&lt;p&gt;The setErrors method takes two arguments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An array for form-specific errors.&lt;/li&gt;
&lt;li&gt;An object for setting input field-specific errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using the same form from above, let’s modify the createUser function to check if the email has “yopmail” in it. If this condition is met, we’ll use the node.setErrors method to set the errors from this form.&lt;/p&gt;

&lt;p&gt;First, we’ll pass a form error with the text “Something happened” and then another error specifically related to the email field that prompts the user to enter a valid email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yopmail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something happened&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please enter a valid email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z36b6LUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/698/0%2AjvnQkexxxpG8Oolh" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z36b6LUQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/698/0%2AjvnQkexxxpG8Oolh" alt="" width="698" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to displaying form-related messages at the bottom of your form, FormKit also provides you with other ways to handle validation and errors related to your form, such as , , and APIs available in &lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/vue when using the Composition API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internationalization in FormKit
&lt;/h3&gt;

&lt;p&gt;Internationalization (i18n) is an important aspect of configuring FormKit. With support for multiple languages, FormKit allows you to provide a localized experience to your users using the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/i18n&lt;/strong&gt; package, which provides support for 43 languages.&lt;/p&gt;

&lt;p&gt;By configuring internationalization in FormKit, you can create forms that cater to users from different language backgrounds, enhancing the usability and accessibility of your application.&lt;/p&gt;

&lt;p&gt;To configure i18n in FormKit, let’s create a formkit.config.js file in the root folder of our application. In this file, we can import the desired language from the &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/i18n&lt;/strong&gt; package and add it to the locales object.&lt;/p&gt;

&lt;p&gt;Finally, we can set this language as the default language for our app by passing it as a string to the locale property.&lt;/p&gt;

&lt;p&gt;Let’s set our default language to German in our config file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;formkit.config.js&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@formkit/i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we save these changes and submit our form again (with validation errors) our default language should change from English to German.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tIV1BwUZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/635/0%2A6lMPk7ICyatYd1-z" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tIV1BwUZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/635/0%2A6lMPk7ICyatYd1-z" alt="" width="635" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that by default, ‘en’ is passed as the locale for your app when it is not otherwise set, and you can customize it, just as we have done. We can also change this value programmatically using any of the following&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;$formkit.setLocale(newLocale) where newLocale is any of the supported languages on FormKit when using the Options API.&lt;/li&gt;
&lt;li&gt;config: With the Composition API, we do not have access to the $formkit plugin. Instead, we make use of the configuration object, which is globally available and can be injected by calling inject(Symbol.for('FormKitConfig')).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The FormKit Schema
&lt;/h3&gt;

&lt;p&gt;Under the hood, the FormKit element is powered by the FormKit schema. The FormKit Schema is a JSON-serializable data format for storing DOM structures and component implementations, including FormKit forms. This schema is capable of generating forms using their respective input types by passing an array of inputs to the  component.&lt;/p&gt;

&lt;p&gt;In this example, we create a login form using FormKit’s schema by passing an array of three objects to .&lt;/p&gt;

&lt;p&gt;These objects contain the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/essentials/schema#html-elements-el"&gt;$el&lt;/a&gt;: short for element.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/essentials/schema#shorthand"&gt;$formkit&lt;/a&gt;: which is short for  in which the value passed here (e.g. email) is assigned as the form type.&lt;/li&gt;
&lt;li&gt;name, label, and validation: standard props for FormKit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;SchemaExample.vue&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKitSchema&lt;/span&gt; &lt;span class="na"&gt;:schema=&lt;/span&gt;&lt;span class="s"&gt;"schema"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FormKitSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@formkit/vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$formkit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required|email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$formkit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required|length:5,16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we check this in the browser, we should get this result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--riAcoXqC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AE8xHu9tny_nwVIqi" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--riAcoXqC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AE8xHu9tny_nwVIqi" alt="" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although this schema was initially designed for building and implementing forms, it can also be used successfully for other DOM elements and components.&lt;/p&gt;

&lt;p&gt;For example, we can utilize the schema to create a div element with multiple nested paragraphs, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a heading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is a paragraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;$el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is another paragraph but with styling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code snippet, we use a div element and add a children property which takes an array of objects. These objects are also elements with properties we have seen earlier excluding the attrs property. The attrs property (an abbreviation for “attributes”) is utilized to set both native HTML and custom attributes on elements. In this instance, we use it to assign the style attribute to the second paragraph element, setting its color to blue.&lt;/p&gt;

&lt;p&gt;When we open the app in our browser, we should see our changes being applied correctly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G9it6TA3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A7JBWdACU-Ga8n7tb" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G9it6TA3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2A7JBWdACU-Ga8n7tb" alt="" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Plugins With FormKit
&lt;/h3&gt;

&lt;p&gt;Out of the box, FormKit ships with a couple of plugins that can improve the general experience of your forms. These plugins include the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://auto-animate.formkit.com/"&gt;AutoAnimate&lt;/a&gt;: useful for adding animations to your application (or forms).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/barcode"&gt;Barcode input&lt;/a&gt;: allows scanning of a variety of 1D and 2D barcode types using cameras connected to your browsing device.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/auto-height-textarea"&gt;Auto-height textarea&lt;/a&gt;: Allows you to give textarea a height that adjusts based on its content.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/floating-labels"&gt;Floating labels&lt;/a&gt;: useful for creating floating labels in your input fields.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/multi-step"&gt;Multi-step input&lt;/a&gt;: makes the process of breaking forms down into steps easy.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/local-storage"&gt;Save to localStorage&lt;/a&gt;: makes it easy to save input/form values to local storage&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://formkit.com/plugins/zod"&gt;Zod validation&lt;/a&gt;: This plugin allows you to enable validation on your forms and input using Zod schema.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These plugins are included in the &lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/addons package that has to be installed separately.&lt;/p&gt;

&lt;p&gt;Let’s install this package in our project using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @formkit/addons
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4uL2SEhl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AXCJMbqhTvNa-s_0Z" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4uL2SEhl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2AXCJMbqhTvNa-s_0Z" alt="" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installation, we can import the plugin we need in our formkit.config.js file.&lt;/p&gt;

&lt;p&gt;For this example, we are going to utilize the &lt;strong&gt;createLocalStorage&lt;/strong&gt; plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@formkit/i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@formkit/vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createLocalStoragePlugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@formkit/addons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;//import plugin here&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;createLocalStoragePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vuemastery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 4 hours&lt;/span&gt;
      &lt;span class="na"&gt;debounce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coming from our existing formkit.config.js code, we import defaultConfig from the &lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/vue package and also the createLocalStoragePlugin from &lt;a class="mentioned-user" href="https://dev.to/formkit"&gt;@formkit&lt;/a&gt;/addons.&lt;/p&gt;

&lt;p&gt;To configure the behavior of this plugin, we add the createLocalStoragePlugin to the plugins array and pass three properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;prefix&lt;/strong&gt; : This is a string that is going to be prefixed to the key in the browser’s local storage. In our case, we’re using ‘vuemastery’ and this means we can identify the value of our form in local storage by checking for a key that starts with ‘vuemastery’. The default is formkit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;maxAge:&lt;/strong&gt; This works the same way &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age"&gt;caching and headers&lt;/a&gt; that are set in the backend work. We have set our example to 4 hours, which means that the values in the form become stale and invalid after 4 hours have elapsed. The default value is 1 hour (**1000 * 60 * 60**).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;debounce&lt;/strong&gt; : We use this value to set a time the plugin has to wait before saving the changes in our form to local storage. This is done to reduce (or increase) the frequency at which the local storage is updated. The default value is 200.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After completing the configuration, we need to specify which forms should use this plugin. To do this, we add the use-local-storage attribute to the forms where we want to persist the data. This will ensure everything works as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"form"&lt;/span&gt; &lt;span class="na"&gt;submit-label=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit=&lt;/span&gt;&lt;span class="s"&gt;"validateForm"&lt;/span&gt; &lt;span class="na"&gt;use-local-storage&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Car brand"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cars"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.car"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Number of seats"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt; &lt;span class="na"&gt;:options=&lt;/span&gt;&lt;span class="s"&gt;"seats"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"numberOfSeats"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.numberOfSeats"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;FormKit&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Price"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"state.price"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="na"&gt;validation=&lt;/span&gt;&lt;span class="s"&gt;"required | min:10000 | max:50000"&lt;/span&gt; &lt;span class="na"&gt;:validation-messages=&lt;/span&gt;&lt;span class="s"&gt;"{
    max: ({ node: { value } }) =&amp;gt; `${value} is above the allowed price range`
   }"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/FormKit&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Toyota&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lexus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Honda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ford&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chevrolet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;numberOfSeats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;car&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code, we added the use-local-storage to the form wrapper. When we fill out this form after adding this plugin and refresh our browser, we will see all the existing data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Gz3vbmN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Arbpc10SmhfO1o_ae" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Gz3vbmN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1000/0%2Arbpc10SmhfO1o_ae" alt="" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This plugin allows additional configuration options such as beforeLoad, an optional asynchronous callback that receives the form data. It enables modification of the localStorage data before applying it to the form. Another option is key, which lets us include an optional key in the localStorage key name. This is useful for associating data with a specific user, among other things.&lt;/p&gt;

&lt;p&gt;For a complete list of configuration options for this &lt;a href="https://formkit.com/plugins/local-storage#installation"&gt;plugin&lt;/a&gt; and other plugins, refer to the official FormKit &lt;a href="https://formkit.com/"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping up
&lt;/h3&gt;

&lt;p&gt;FormKit enhances the native HTML form experience with its user interface and customizable features. It offers a range of essential tools for our form-building process.&lt;/p&gt;

&lt;p&gt;FormKit’s &lt;a href="https://formkit.com/essentials/styling"&gt;styling&lt;/a&gt; configuration makes it easy to customize your input’s appearance. This is particularly useful when working with a project’s theme or style guide. Moreover, FormKit provides &lt;a href="https://formkit.com/essentials/icons"&gt;icons&lt;/a&gt; as visual cues for your form’s purpose.&lt;/p&gt;

&lt;p&gt;FormKit also allows building custom inputs that utilize its features. If you prefer creating your components, you can read more about custom inputs &lt;a href="https://formkit.com/essentials/custom-inputs"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To understand the technical aspects of the FormKit library and learn how to use it effectively, refer to the &lt;a href="https://formkit.com/essentials/configuration"&gt;configuration&lt;/a&gt; and &lt;a href="https://formkit.com/essentials/architecture"&gt;architecture&lt;/a&gt; documentation. These resources provide detailed information about node and the form layout and structure with FormKit.&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://www.vuemastery.com/blog/effortless-forms-w-formkit"&gt;&lt;em&gt;https://www.vuemastery.com&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on March 22, 2024.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>vue</category>
    </item>
    <item>
      <title>Vue tools to master in 2024</title>
      <dc:creator>Vue Mastery team</dc:creator>
      <pubDate>Wed, 27 Dec 2023 10:48:32 +0000</pubDate>
      <link>https://forem.com/vuemastery/vue-tools-to-master-in-2024-32mo</link>
      <guid>https://forem.com/vuemastery/vue-tools-to-master-in-2024-32mo</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jTkN_eJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AMB-cRe1sEslCkATGQ58_gw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jTkN_eJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2AMB-cRe1sEslCkATGQ58_gw.jpeg" alt="Vue tools to master in 2024" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by&lt;/em&gt; &lt;a href="https://timiomoyeni.com/"&gt;&lt;em&gt;Timi Omoyeni&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we approach a new year, it is crucial to stay updated on the latest topics and tools to enhance your skills as an engineer. This can be challenging due to the continuously evolving web development landscape and the rapid pace of innovations in the Vue.js ecosystem. To help you stay ahead, let us explore some topics that are a great starting point for becoming a better Vue.js developer in 2024.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue 3 Vapor mode 🌬️
&lt;/h3&gt;

&lt;p&gt;If you’ve been following Vue Creator Evan You, you’ve heard him talk a lot recently about Vapor mode. The name is admittedly quite cool, but what does &lt;em&gt;vapor&lt;/em&gt; mode mean, after all?&lt;/p&gt;

&lt;p&gt;In short, Vapor mode is an alternative compilation strategy aimed at improving the performance of your Vue.js application. It takes the same Vue single file component and compiles it into JavaScript output that is more performant, uses less memory, and requires less runtime support code compared to the current Virtual DOM-based output. It was inspired by Solid.js — a simple and performant reactivity for building user interfaces.&lt;/p&gt;

&lt;p&gt;The benefits of using Vapor include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More performant&lt;/li&gt;
&lt;li&gt;Uses less memory&lt;/li&gt;
&lt;li&gt;requires less runtime support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is an experimental feature that was first announced in 2022 and has its own &lt;a href="https://github.com/vuejs/core-vapor"&gt;repo&lt;/a&gt;. It compiles differently when enabled in a component, resulting in a lighter rendering code and improving performance. To achieve this, it only supports a subset of Vue features, including the Composition API and .&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;As Vapor mode gets closer to its official release, Vue Mastery will be publishing exclusive content on how to use it for its fullest impact.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="vue-2-end-of-life" href="#vue-2-end-of-life" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Vue 2 End of Life 🪦
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;In 2022, Vue 3.x became the default version for creating new Vue applications. Now, as of December 31 of 2023, Vue 2 reaches its end of life. Evan You recently &amp;lt;a href="https://blog.vuejs.org/posts/vue-2-eol"&amp;gt;reminded us&amp;lt;/a&amp;gt; of this update and the possible next steps and recommendations for Vue 2 users.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;After its EOL, Vue 2 will still be available on all distribution channels and package managers but will no longer receive updates (bug and security fixes).&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;What does this mean for the community? The following will be deprecated:&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;All major and minor versions of Vue 2 core&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;vue-router versions exclusively supporting Vue 2 (3.x and below)&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Vuex versions with exclusive support for Vue 2 (3.x and below)&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;With these updates in mind, developers still working with this version have been presented with a handful of options ranging from migrating to Vue 3 to the proposed &amp;lt;a href="https://github.com/vuejs/vue/releases/tag/v2.7.16"&amp;gt;v2.7.16&amp;lt;/a&amp;gt;, which is the final release of Vue 2. You can also check out this article on &amp;lt;a href="https://v2.vuejs.org/lts/"&amp;gt;LTS&amp;lt;/a&amp;gt; and the &amp;lt;a href="https://blog.vuejs.org/posts/vue-2-eol"&amp;gt;EOL&amp;lt;/a&amp;gt; to help you decide on the best option for your Vue 2 applications.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Keep in mind, we have an entire library of &amp;lt;a href="https://www.vuemastery.com/courses/"&amp;gt;Vue 3 courses&amp;lt;/a&amp;gt; here on Vue Mastery to help Vue 2 developers transition.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="nuxt-tools" href="#nuxt-tools" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Nuxt Tools ⛰️
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;This year, we had the release of several interesting tools that are either powered by Nuxt or aimed at improving the overall developer experience of building with Nuxt.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;These updates include:&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;Nuxt DevTools&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Nuxt Studio&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Nuxt UI&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Nuxt Icon&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="nuxt-devtools" href="#nuxt-devtools" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Nuxt DevTools
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Nuxt DevTools was first announced on the 27th of March 2023 and has received a lot of positive feedback from the community. It was created to help developers understand and improve the overall experience of developing their Nuxt app. In November 2023, Nuxt DevTools v1.0 was released, and with this release, it is enabled by default in new Nuxt projects( from Nuxt v3.8).&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Some of the Nuxt DevTools features include:&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;In-app devTools — floating panel visible anywhere in the app that opens the devtools.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Pages view — Shows a list of all the pages registered in your app with easy navigation.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Module management — allows you to view all the modules registered in your app with the links to their documentation and repositories.&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;You can find out more about the DevTools by reading &amp;lt;a href="https://www.vuemastery.com/blog/exploring-the-nuxt-3-devtools/"&amp;gt;this article&amp;lt;/a&amp;gt; on Vue Mastery.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="nuxt-studio" href="#nuxt-studio" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Nuxt Studio
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Another exciting update that you should pay attention to in 2024 is Nuxt Studio. This is a powerful platform specifically designed for creating, editing, and deploying Nuxt 3 applications that are reliant upon &amp;lt;a href="https://content.nuxtjs.org/"&amp;gt;Nuxt Content V2&amp;lt;/a&amp;gt;, which is a git-based CMS powered by Markdown.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Nuxt Studio is useful for content-heavy projects like blogs, documentation, and portfolios. With Nuxt Studio, developers have an easier way to collaborate with non-developer members of their team (such as marketing and design) to make contributions to their sites without the need to be technical. It comes with several features including the Studio Editor, which improves the process of making changes and collaboration among teams.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;You can find out more about Nuxt Studio in this &amp;lt;a href="https://www.vuemastery.com/blog/touring-nuxt-studio"&amp;gt;article&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="nuxt-ui" href="#nuxt-ui" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Nuxt UI
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Nuxt UI simplifies the creation of stunning and responsive web applications with its comprehensive collection of fully styled and customizable UI components designed for Nuxt apps. It was created using technologies like Headless UI and Tailwind CSS and was also developed by Nuxt Labs.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;It has a collection of over 40 components built for Nuxt that have been beautifully designed and are also fully customizable.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Some of its features include:&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;Dark mode support&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Support for LTR and RTL languages&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Bundled icons&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;HMR support through Nuxt App Config&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;

&amp;lt;p&amp;gt;You can find out more about Nuxt UI in this &amp;lt;a href="https://www.vuemastery.com/blog/build-an-x-clone-w-nuxt-ui/"&amp;gt;article&amp;lt;/a&amp;gt; and the &amp;lt;a href="https://ui.nuxt.com/getting-started/installation"&amp;gt;official documentation&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="nuxt-3-updates" href="#nuxt-3-updates" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Nuxt 3 updates
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Nuxt 3 was officially released in November of 2023 and has been well received by the community as it is based on Vite, Vue 3 and &amp;lt;a href="https://nitro.unjs.io/"&amp;gt;Nitro&amp;lt;/a&amp;gt; and comes with first-class TypeScript support.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;With this release, it set the ball rolling for a series of exciting excitements that came from the Nuxt community in 2023. These updates range from edge-side rendering to several version updates including interactive server components. This feature makes it possible to play around with interactive components within Nuxt server components. Although it is new and experimental, it’s an exciting change&amp;lt;/p&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="formkit" href="#formkit" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  FormKit 📝
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;Another exciting tool we have seen as a result of Vue 3 and Nuxt 3 is FormKit. As the name implies, FormKit is a comprehensive form-building framework for Vue developers that makes authoring high-quality, production-ready forms faster and more accessible, with better DX, UX, and less code.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;The idea behind this tool is for it to be the one-stop shop for everything &amp;lt;em&amp;gt;form&amp;lt;/em&amp;gt;. This can range from the several input field UI components that are available to both free and Pro users, to the FormKit Schema that helps you generate and implement forms and other DOM elements.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;Some of its features include:&amp;lt;/p&amp;gt;

&amp;lt;ol&amp;gt;
&amp;lt;li&amp;gt;Validation — FormKit offers configurations and an easily extendable validation prop that can be used to validate the values of the fields&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Plugins — It also ships with plugins like &amp;lt;a href="https://formkit.com/plugins/auto-animate"&amp;gt;AutoAnimate&amp;lt;/a&amp;gt; and &amp;lt;a href="https://formkit.com/plugins/local-storage"&amp;gt;Save to localStorage&amp;lt;/a&amp;gt; that improve the general user experience while using forms built using FormKit.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Inputs — Support for up to 20 of the popular inputs that are used when creating forms.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;Internationalization — Support for several languages with multi-language support (you can switch between several languages at a time).&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;h3&amp;gt;
  &amp;lt;a name="elevate-your-code-in-2024" href="#elevate-your-code-in-2024" class="anchor"&amp;gt;
  &amp;lt;/a&amp;gt;
  Elevate your code in 2024
&amp;lt;/h3&amp;gt;

&amp;lt;p&amp;gt;It’s clear that Vue 3’s impact on the Vue ecosystem has been vast and still continues to spread into the tools, libraries and conventions that Vue developers use now and into the future.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;To help you stay at the top of your game in 2024, Vue Mastery is currently offering &amp;lt;a href="https://www.vuemastery.com/holiday/"&amp;gt;60% off a full year&amp;lt;/a&amp;gt; access to our library of 50+ courses and 205 conference talks so that you can learn what you need to succeed as a Vue developer and catapult your career.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Originally published at&amp;lt;/em&amp;gt; &amp;lt;a href="https://www.vuemastery.com/blog/vue-tools-to-master-in-2024/"&amp;gt;&amp;lt;em&amp;gt;https://www.vuemastery.com&amp;lt;/em&amp;gt;&amp;lt;/a&amp;gt; &amp;lt;em&amp;gt;on December 27, 2023.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;hr&amp;gt;
&lt;/p&gt;

</description>
      <category>coding</category>
      <category>vue</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
