<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Vue Mastery - Medium]]></title>
        <description><![CDATA[Access the ultimate collection of Vue.js courses at https://www.vuemastery.com/ - Medium]]></description>
        <link>https://medium.com/vue-mastery?source=rss----99ddd8223b51---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Vue Mastery - Medium</title>
            <link>https://medium.com/vue-mastery?source=rss----99ddd8223b51---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 09 May 2026 12:38:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/vue-mastery" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Top AI Tools for Developers]]></title>
            <link>https://medium.com/vue-mastery/top-ai-tools-for-developers-b598235f1f18?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/b598235f1f18</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[developer-tools]]></category>
            <category><![CDATA[vue]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Wed, 02 Apr 2025 19:00:30 GMT</pubDate>
            <atom:updated>2025-04-02T19:00:30.543Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Top AI Tools for Developers" src="https://cdn-images-1.medium.com/max/1024/1*z_Z_Cbz0gwL8g4krPheXJQ.jpeg" /></figure><p><em>Written by David Nwadiogbu</em></p><p>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.</p><p>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.</p><p>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.</p><h3>How AI Is Enhancing Developer Productivity</h3><p>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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*PEg0RiVNS_FOfs6k" /></figure><p>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.</p><p>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.</p><h3>Top AI Tools for Developers</h3><ol><li><strong>Cursor</strong>: 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.</li><li><strong>GitHub Copilot</strong>: GitHub Copilot integrates with your editor and, like Cursor, lets you select your preferred AI agent. It also includes an <a href="https://github.com/features/copilot/extensions">extensions feature</a> that integrates external devtools such as Docker, Sentry, or GitBook into the Copilot chat.</li><li><strong>Claude</strong>: 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 <a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview#install-and-authenticate">Claude Code</a>, an agentic coding tool that lives in your terminal, has context on your codebase, and helps you code faster through natural language commands.</li><li><strong>v0.dev</strong>: 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.</li><li><strong>Bolt.new</strong>: 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.</li><li>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.</li></ol><p>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.</p><h3>Ways to use AI for Development</h3><h3>Prototyping</h3><p>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.</p><p><strong>Example</strong> To demonstrate <a href="http://bolt.new/">bolt.new</a>’s prototyping capabilities, I gave it the following prompt: <em>“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.”</em></p><p>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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_gg5K0QEFf3E-PLY" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dBcIvWqGWd3e9-rZ" /></figure><p><em>Suitable tools for prototyping: Bolt.new, v0.dev</em></p><h3>Code generation</h3><p>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.</p><p><strong>Example</strong></p><p>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.</p><p>Here’s a sample prompt written in cursor chat:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0MS1g1eODUyRNtgc" /></figure><p>Cursor went on to first structure the components directory and then create every component on the list provided with full customization support.</p><p><em>Suitable tools for code generation: Cursor, Copilot</em></p><h3>Pair programming</h3><p>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.</p><p><strong>Example</strong></p><p>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’.</p><p>Here’s an example where we add the dietary filtering feature:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Np5Uj7UWZtsonRP6" /></figure><p>adding a feature to filter recipes based on dietary restrictions</p><p>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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*j8cq2_jny7wNFFPb" /></figure><p><em>Suitable tools for pair programming: Cursor, Copilot</em></p><h3>Debugging</h3><p>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.</p><p><strong>Example</strong></p><p>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.</p><p>Here’s a User Profile component that demonstrates these common errors:</p><p><strong>📄Userprofile.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;h1&gt;User Profile&lt;/h1&gt;<br>    &lt;p&gt;Name: {{ user.name }}&lt;/p&gt;<br>    &lt;p&gt;Email: {{ user.email }}&lt;/p&gt;<br>    &lt;div ref=&quot;profileDetails&quot; class=&quot;profileDetails&quot;&gt;<br>      &lt;p&gt;User details Loaded!&lt;/p&gt;<br>    &lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { ref, onMounted } from &#39;vue&#39;;<br>const user = ref({});<br>const profileDetails = ref(null);<br>const fetchUserData = async () =&gt; {<br>  // Simulate an API call with a delay<br>  setTimeout(() =&gt; {<br>    user.value = {<br>      name: &quot;John Doe&quot;,<br>      email: &quot;john.doe@example.com&quot;,<br>    };<br>  }, 3000);<br>};<br>const showDetails = () =&gt; {<br>  console.log(profileDetails.value);<br>  if (profileDetails.value) {<br>    profileDetails.value.style.display = &#39;block&#39;;<br>  }<br>};<br>onMounted(() =&gt; {<br>  fetchUserData();<br>  showDetails(); // Attempt to access DOM element immediately<br>});<br>&lt;/script&gt;<br>&lt;style scoped&gt;<br>.profileDetails {<br>  display: none;<br>}<br>&lt;/style&gt;</pre><p>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.</p><p>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.</p><p>Let’s see the results when we ask Claude for help.</p><p>Prompt:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DpNWEWlY1CvcVNEy" /></figure><p>Claude’s response:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-Xim6_e22usBaD-a" /></figure><p>Updated <strong>📄Userprofile.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div&gt;<br>    &lt;h1&gt;User Profile&lt;/h1&gt;<br>    &lt;p v-if=&quot;isLoading&quot;&gt;Loading user data...&lt;/p&gt;<br>    &lt;template v-else&gt;<br>      &lt;p&gt;Name: {{ user.name }}&lt;/p&gt;<br>      &lt;p&gt;Email: {{ user.email }}&lt;/p&gt;<br>      &lt;div class=&quot;profileDetails&quot;&gt;<br>        &lt;p&gt;User details Loaded!&lt;/p&gt;<br>      &lt;/div&gt;<br>    &lt;/template&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import { ref, onMounted } from &#39;vue&#39;;<br>const user = ref({});<br>const isLoading = ref(true);<br>const fetchUserData = async () =&gt; {<br>  isLoading.value = true;<br>  <br>  // Simulate an API call with a delay<br>  setTimeout(() =&gt; {<br>    user.value = {<br>      name: &quot;John Doe&quot;,<br>      email: &quot;john.doe@example.com&quot;,<br>    };<br>    isLoading.value = false;<br>  }, 3000);<br>};<br>onMounted(() =&gt; {<br>  fetchUserData();<br>});<br>&lt;/script&gt;<br>&lt;style scoped&gt;<br>.profileDetails {<br>  margin-top: 20px;<br>  padding: 10px;<br>  background-color: #e6f7ff;<br>  border-radius: 4px;<br>}<br>&lt;/style&gt;</pre><p>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!</p><p>Suitable tool: Claude, ChatGPT</p><h3>Tips for an effective AI Development Workflow</h3><p>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:</p><ul><li><strong><em>Be specific with your prompt:</em></strong> These tools need as much context as possible in order to provide suitable help. It is encouraged that you <em>over</em>-explain. This could mean being specific with the language/framework or coding style you want the AI to use and also what you <em>don’t</em> 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.</li><li><strong><em>Give your AI a persona</em></strong>: 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.</li><li><strong><em>Use Analogies</em></strong>: 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.</li><li><strong><em>Prioritize learning and fact checking</em></strong>: 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 <strong>always</strong> verify AI-generated output against trusted documentation.</li></ul><h3>Where to go from here</h3><p>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.</p><p>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 <em>augment</em> 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.</p><p>If you’d like to get started with AI development, we have a great <a href="https://www.vuemastery.com/courses/programming-an-ai-powered-app/analyze-text-feature/">course</a> here on Vue mastery to help you get started.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/top-ai-tools-for-developers/"><em>https://www.vuemastery.com</em></a><em> on April 2, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b598235f1f18" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/top-ai-tools-for-developers-b598235f1f18">Top AI Tools for Developers</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vue Native? Vue + Lynx]]></title>
            <link>https://medium.com/vue-mastery/vue-native-vue-lynx-88cbf44710e5?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/88cbf44710e5</guid>
            <category><![CDATA[lynx]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[vue]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Mon, 31 Mar 2025 23:29:11 GMT</pubDate>
            <atom:updated>2025-03-31T23:29:11.488Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Vue Native? Vue + Lynx" src="https://cdn-images-1.medium.com/max/1024/1*-sRDIoT9BdEyG1jNvu4c_g.png" /></figure><p><em>Written by David Nwadiogbu</em></p><p>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!</p><p>Lynx is a UI framework built on JavaScript that allows developers to build both web and mobile applications that feel native thereby fulfilling the “<em>write once, run anywhere</em>” dream frontend/UI developers have pursued for a long time.</p><p>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.</p><h3>History of Mobile development with Vue</h3><p>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.</p><h3>1. Nativescript-Vue</h3><p>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.</p><h3>2. Ionic Vue with Capacitor</h3><p>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.</p><h3>3. Quasar Framework</h3><p>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.</p><p>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.</p><p>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.</p><h3>Why Lynx for Mobile Vue Apps?</h3><p>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.</p><h3>1. Lynx is Performance Driven</h3><p>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 <strong>UI/main</strong> thread and the <strong>Application/Background</strong> thread.</p><p><em>Let’s break down these terms:</em></p><p><strong>The UI/main thread</strong>: 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 <a href="https://rspack.dev/">Rspack</a>).</p><p><strong>The Background thread:</strong> This handles your application logic, such as data processing, API calls, and state management.</p><p>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.</p><p>This performance improvement can already be seen powering some parts of the Tiktok ecosystem such as its search, studio, and live.</p><h3>2. Lynx takes a Web-first Approach</h3><p>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.</p><p>It takes it a step further by offering direct support for modern CSS features and visual effects like gradients, clipping and masking.</p><h3>3. Lynx uses Components</h3><p>Lynx encourages the component way of building UIs as seen in modern JS frameworks like Vue.</p><p>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.</p><h3>4. Lynx is framework and platform agnostic</h3><p>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.</p><p>This design decision opens up possibilities for the Vue community to leverage Lynx’s powerful features while maintaining their preferred development environment.</p><p>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.</p><h3>Vue + Lynx</h3><p>Lynx launched with support for React (ReactLynx) as their initial frontend framework, and since then there has been a lot of <a href="https://github.com/lynx-family/lynx/issues/193">discussions</a> around creating its Vue counterpart.</p><p>The most popular of these discussions came from this <a href="https://x.com/youyuxi/status/1898663514581168173">post</a> 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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0BBiJIgP9N34mzGv" /></figure><p>Xuan Huang, a software engineer on the Lynx team and author of the <a href="https://lynxjs.org/blog/lynx-unlock-native-for-more.html">blog post</a> that announced Lynx also <a href="https://x.com/Huxpro/status/1898837980292493721">shared</a> the team’s plans to have Vue on Lynx</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1TbkjdS65M-PE0r2" /></figure><p>And in less than 24hrs, Rahul Vashishtha, a member of the Vue community, had started working on a <a href="https://github.com/rahul-vashishtha/lynx-stack/tree/lynx-vue-implementation">prototype</a> for the project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xG4rUkl8mx9zUQ21" /></figure><p>Although, for now, it’s just a prototype, we believe the Vue community will really benefit from a VueLynx integration.</p><p>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.</p><p>Although, there’s no official Vue integration yet, you can already start integrating Lynx with your existing Vue app.</p><h3>Example Vue + Lynx Code</h3><p>Here is an example of what Vue + Lynx code may look like. This example code is from the Github prototype.</p><pre>&lt;script&gt;<br>import lynxLogo from &#39;./assets/lynx-logo.png&#39;<br><br>export default {<br>  data() {<br>    return {<br>      title: &quot;Hello VueLynx&quot;,<br>      message: &quot;Welcome to Vue 3 in Lynx!&quot;,<br>      count: 0,<br>    };<br>  },<br>  methods: {<br>    increment() {<br>      this.count++;<br>    },<br>  },<br>};<br>&lt;/script&gt;<br>&lt;template&gt;<br>  &lt;view&gt;<br>    &lt;image :src=&quot;lynxLogo&quot; className=&#39;Logo--lynx&#39; /&gt;<br>    &lt;text className=&#39;Title&#39;&gt;Vue&lt;/text&gt;<br>    &lt;text className=&#39;Subtitle&#39;&gt;on Lynx&lt;/text&gt;<br>    &lt;h1&gt;{{ title }}&lt;/h1&gt;<br>    &lt;p&gt;{{ message }}&lt;/p&gt;<br>    &lt;button @click=&quot;increment&quot;&gt;Count: {{ count }}&lt;/button&gt;<br>  &lt;/view&gt;<br>&lt;/template&gt;<br></pre><p>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 &lt;view&gt; which is often used for layout capabilities, stylization, and wrapping other elements, the &lt;text&gt; element is used to display text content and the &lt;image&gt; element to display images.</p><p>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</p><p>Vue + Lynx will become a reality after the following build steps have been completed:</p><ol><li>Make Vue compiler work with rspeedy: This step will compile Vue’s SFC to Lynx dual-thread compatible format splitting the &lt;template&gt; code to the main thread and &lt;script&gt; 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</li><li>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 <a href="https://lynxjs.org/api/engine/element-api/__CreateElement.html">Lynx’s Element API</a>.</li><li>Implement compile-time tools to split code into two threads: This requires additional API change to Vue like supporting &lt;script main&gt; to compile code to main thread script.</li></ol><h3>What’s Next for Vue + Lynx?</h3><p>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.</p><p>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 <a href="https://lynxjs.org/guide/start/integrate-with-existing-apps.html#platform=ios">recommend</a> that you build your entire app from scratch using the framework, but rather integrate it with already existing apps.</p><p>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.</p><p>With that said, we encourage you to support this development by contributing to the project if you can.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/vue-native-vue-lynx/"><em>https://www.vuemastery.com</em></a><em> on March 28, 2025.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=88cbf44710e5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/vue-native-vue-lynx-88cbf44710e5">Vue Native? Vue + Lynx</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a Greeting Card Maker w/ Vue 3 + Satori]]></title>
            <link>https://medium.com/vue-mastery/build-a-greeting-card-maker-w-vue-3-satori-c2ef115489c2?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/c2ef115489c2</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[vue]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Thu, 19 Dec 2024 20:07:45 GMT</pubDate>
            <atom:updated>2024-12-19T20:07:45.657Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Build a Greeting Card Maker with Vue 3 and Satori" src="https://cdn-images-1.medium.com/max/1024/1*hghrxBcKLWvvD6NO94_fzQ.jpeg" /></figure><p><em>Written by </em><a href="https://x.com/dubem_design"><em>Dubem Izuorah</em></a></p><p>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.</p><h3>What we are going to be building</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fQ5H0IqPrWDEco4w" /></figure><p>Screenshot of Holiday Greeting Card Maker interface</p><p>By the end of this tutorial, you’ll have:</p><ul><li>A fully functional web app for creating personalized holiday cards</li><li>Hands-on experience with Vue 3, Nuxt, and some powerful rendering libraries</li><li>An understanding of how to dynamically generate graphics in the browser</li></ul><h3>Project Initialization</h3><p>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:</p><pre>npx nuxi init christmas-card-maker <br>cd christmas-card-maker<br>yarn install</pre><p>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!</p><h3>Installing Our Toolkit</h3><p>Now, we’ll add the libraries that will make our card generation magic happen. Run the following commands on your terminal:</p><pre>yarn add @vue/server-renderer @vueuse/core nuxt satori satori-html<br>yarn add -D @nuxtjs/tailwindcss</pre><p>Let me break down why we’re choosing each of these:</p><ul><li><a href="https://vuejs.org/guide/scaling-up/ssr.html#server-rendering">@vue/server-renderer</a>: Allows us to render Vue components &amp; props as HTML strings - crucial for our SVG generation</li><li><a href="https://github.com/vercel/satori">satori</a>: Converts our HTML and CSS string into beautiful SVG graphics</li><li><a href="https://github.com/natemoo-re/satori-html">Satori HTML</a>: A helper library for rendering HTML content compatible with Satori</li><li><a href="https://vueuse.org/">@vueuse/core</a>: Provides helpful utilities like local storage, watchDebounce and many more</li><li><a href="https://tailwindcss.nuxtjs.org/getting-started/installation">@nuxtjs/tailwindcss</a>: Gives us rapid styling capabilities</li></ul><h3>Configuring our Project</h3><p>Let’s update our nuxt.config.ts to set up our development environment:</p><p>📄 <strong>nuxt.config.ts</strong></p><pre>export default defineNuxtConfig({<br>  ssr: false,<br>  devtools: { enabled: true },<br>  modules: [&quot;@nuxtjs/tailwindcss&quot;],<br>});</pre><p>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.</p><h3>Coding our Card Maker</h3><p>Now that we’re all setup, let’s dive into creating our Holiday Card Maker step-by-step.</p><h3>Step 1. Implementing the Card Maker Interface</h3><p>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.</p><p>Here’s what the base layout will look like:</p><p>📄 <strong>app.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div class=&quot;flex justify-center h-screen items-start bg-gray-100 pt-20&quot;&gt;<br>    &lt;div class=&quot;border p-8 w-96 bg-white flex flex-col gap-8&quot;&gt;<br>      &lt;h1 class=&quot;text-xl font-medium&quot;&gt;Christmas Greeting Card Maker&lt;/h1&gt;<br>      &lt;div class=&quot;border aspect-square banner-here relative&quot; v-html=&quot;svg&quot;&gt;&lt;/div&gt;<br>      &lt;form class=&quot;flex flex-col gap-4&quot;&gt;<br>        &lt;input type=&quot;text&quot; v-model=&quot;form.name&quot; class=&quot;px-4 py-1 border w-full&quot; placeholder=&quot;Enter name&quot; /&gt;<br>        &lt;input type=&quot;text&quot; v-model=&quot;form.greeting&quot; class=&quot;px-4 py-1 border w-full&quot; placeholder=&quot;Enter greeting&quot; /&gt;<br>        &lt;input type=&quot;file&quot; accept=&quot;.png, .jpg, .jpeg&quot; @change=&quot;handleFileChange&quot; class=&quot;px-4 py-1 border w-full&quot; /&gt;<br>        &lt;small class=&quot;text-gray-400&quot;&gt;Must be an image below 100kb, png, jpeg, or jpg formats only.&lt;/small&gt;<br>        &lt;button class=&quot;bg-indigo-500 text-white px-4 py-2&quot; @click.prevent=&quot;downloadSvgAsJpeg(svg)&quot;&gt;Download&lt;/button&gt;<br>      &lt;/form&gt;<br>    &lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;ts&quot;&gt;<br>...</pre><p><strong>So what’s happening here?</strong></p><ol><li><strong>Card Preview:</strong> The &lt;div&gt; with v-html=&quot;svg&quot; is where our card will appear as an SVG once it’s generated.</li><li><strong>Form Fields:</strong></li></ol><ul><li>Two text fields: name and greeting. These will dynamically update the card when the user types.</li><li>File input: Allows users to upload an image.</li></ul><ol><li><strong>File Restrictions:</strong> 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.</li><li><strong>Download Button:</strong> This triggers the downloadSvgAsJpeg function, which saves the card as a JPEG image.</li></ol><h3>Step 2. Setting up Dependencies</h3><p>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.</p><p>📄 <strong>app.vue</strong></p><pre>...<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;ts&quot;&gt;<br>import { createSSRApp } from &quot;vue&quot;;<br>import { renderToString } from &quot;@vue/server-renderer&quot;;<br>import { useLocalStorage, watchDebounced } from &quot;@vueuse/core&quot;;<br>import satori from &quot;satori&quot;;<br>import { html } from &quot;satori-html&quot;;<br>import ChristmasCard from &quot;./components/ChristmasCard.vue&quot;;<br>const form = useLocalStorage(&quot;app-form&quot;, {<br>  name: &quot;&quot;,<br>  greeting: &quot;Merry Christmas&quot;,<br>  photo: null,<br>});<br>const svg = ref(&quot;&quot;);<br>const fonts = ref([]);<br>...<br>&lt;/script&gt;</pre><p><strong>Why do we need this setup?</strong></p><ol><li><strong>createSSRApp</strong> and <strong>renderToString</strong>: Render Vue components to HTML strings by virtually mounting the component and its props and rendering out its final output.</li><li><strong>useLocalStorage</strong>: Save form data locally, so users don’t lose progress if they reload.</li><li><strong>watchDebounced</strong>: Helps us prevent performance issues by delaying the processing of user inputs until a certain duration after typing has stopped.</li><li><strong>satori</strong>: Convert HTML to SVG for our graphics.</li><li><strong>satori-html</strong>: Parse HTML strings into a format satori understands.</li><li>The fonts ref uses <strong>useLocalStorage</strong> to persists the user’s inputs. If you refresh the page, we won’t lose the form input and can continue where we left.</li><li>The svg and fonts variables will store the generated card design and loaded font data.</li></ol><h3>Step 3. Creating the Card Template</h3><p>Next, let’s create the card template component we imported earlier. This is where the magic happens.</p><p>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.</p><p>📄 <strong>components/ChristmasCard.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div class=&quot;w-full bg-red-500 h-full flex flex-col text-white text-7xl font-bold p-10 items-center justify-center&quot;<br>    style=&quot;background-image: url(/img/sincerely-media-8EhZobADF8M-unsplash.jpg);&quot;&gt;<br>    &lt;div class=&quot;font-bold mb-8 &quot; style=&quot;font-size: 100px;&quot;&gt; {{ greeting }}&lt;/div&gt;<br>    &lt;img v-if=&quot;photo&quot; :src=&quot;photo&quot; class=&quot;rounded-full border-4 border-red-900 mb-8&quot; style=&quot;width:400px;height:400px&quot;&gt;<br>    &lt;div class=&quot;bg-red-500 px-2 py-2&quot;&gt;From&lt;/div&gt;<br>    &lt;div&gt; {{ name || &#39;Add your name here&#39; }}&lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;ts&quot;&gt;<br>defineProps([&#39;name&#39;, &#39;greeting&#39;, &#39;photo&#39;])<br>&lt;/script&gt;</pre><p><strong>Here’s what’s happening:</strong></p><ul><li><strong>Placeholders:</strong> {{ greeting }} and {{ name }} dynamically display user inputs.</li><li><strong>Image Upload:</strong> If the user uploads an image, it appears in the card.</li><li><strong>Background:</strong> We’re adding a festive look with a red background image from unsplash.</li></ul><h3>Step 4. Loading Fonts and Initializing the App</h3><p>Back to the <strong>app.vue</strong> 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.</p><p>📄 <strong>app.vue</strong></p><pre>// Back to app.vue<br>...<br>onMounted(async () =&gt; {<br>  fonts.value = await loadFonts([{ name: &quot;InstrumentSans&quot;, url: &quot;/fonts/InstrumentSans-Regular.ttf&quot; }]);<br>  refreshGraphics();<br>});<br><br>watchDebounced(form, refreshGraphics, { deep: true, debounce: 500, maxWait: 1000 });<br>async function loadFonts(fonts) {<br>  return Promise.all(<br>    fonts.map(async (font) =&gt; ({<br>      ...font,<br>      data: await (await fetch(font.url)).arrayBuffer(),<br>    }))<br>  );<br>}<br>...</pre><p><strong>Breaking it down:</strong></p><ul><li>We use the onMounted hook, which is auto-imported in Nuxt 3, to load and store the fonts needed for generating the graphics.</li><li>You can use any .ttf font file. To get the same font I used, “Instrument Sans” font, visit <a href="https://fonts.google.com/specimen/Instrument+Sans">Google Fonts</a>, 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.</li><li>Inside onMounted, we call refreshGraphics to generate the initial SVG graphics once the font has loaded.</li><li>We use watchDebounced to track changes in the form and update the graphics 500ms after the last update. <em>Note: this is a performance trick.</em></li><li>The loadFonts function loads the required fonts all at once using Promise.all instead of sequentially. <em>Another performance trick to note.</em></li></ul><h3>Step 5. Generating SVG from Vue Component</h3><p>Here’s where we tie everything together. We’ll convert the ChristmasCard component into an SVG.</p><p>📄 <strong>app.vue</strong></p><pre>...<br>async function refreshGraphics() {<br>  const content = await renderToHTML(ChristmasCard, form.value);<br>  const markup = html(content);<br>  svg.value = await satori(markup, { width: 1080, height: 1080, fonts: fonts.value });<br>}<br>...</pre><p><strong>Let me explain:</strong></p><ul><li>We first use the renderToHTML utility function to convert the ChristmasCard.vue component imported earlier into HTML string</li><li>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</li><li>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</li><li>The resulting svg string is stored in the svg.value ref, which we used in the <strong>app.vue</strong> template section to display the svg on the viewport. See &lt;div class=&quot;border aspect-square banner-here relative&quot; v-html=&quot;svg&quot;&gt;&lt;/div&gt; in the template section</li></ul><h3>Step 6. Utility Functions</h3><p>Let’s wrap up everything we’ve been working on by adding some essential utility functions for our code and template features.</p><p><strong>Adding Image Upload Support</strong></p><p>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.</p><p>Paste this below the refreshGraphics code:</p><p>📄 <strong>app.vue</strong></p><pre>...<br>async function handleFileChange(event: Event) {<br>  const file = (event.target as HTMLInputElement)?.files?.[0];<br>  if (file &amp;&amp; file.size &gt; 100 * 1024) throw new Error(&quot;File size must be below 100kb&quot;);<br>  const reader = new FileReader();<br>  reader.onload = () =&gt; (form.value.photo = reader.result as string);<br>  reader.readAsDataURL(file);<br>}<br>...</pre><p><strong>Why check file size?</strong> We’re limiting the file size to ensure smooth performance. Anything larger could slow things down, so 100KB is a safe upper limit.</p><h3>Converting Vue component to HTML string</h3><p>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.</p><pre>...<br>async function renderToHTML(Component, props = {}) {<br>  return await renderToString(createSSRApp(Component, props));<br>}<br>...</pre><h3>Explanation:</h3><ul><li><strong>createSSRApp</strong>: This function is used to create a server-side rendered (SSR) Vue application instance, which can be rendered to an HTML string.</li><li><strong>renderToString</strong>: This renders the Vue component (GreetingCard) to an HTML string, passing any props needed by the component.</li></ul><h3>Downloading the Card as a JPEG</h3><p>Finally, let’s add code that will enable us to download our card as a JPEG.</p><p>📄 <strong>app.vue</strong></p><pre>...<br>function downloadSvgAsJpeg(svgString, filename = &quot;image.jpeg&quot;) {<br>  const blob = new Blob([svgString], { type: &quot;image/svg+xml&quot; });<br>  const url = URL.createObjectURL(blob);<br>  const img = new Image();<br><br>img.onload = () =&gt; {<br>    const canvas = document.createElement(&quot;canvas&quot;);<br>    canvas.width = canvas.height = 1080;<br>    canvas.getContext(&quot;2d&quot;)?.drawImage(img, 0, 0, 1080, 1080);<br>    const link = document.createElement(&quot;a&quot;);<br>    link.href = canvas.toDataURL(&quot;image/jpeg&quot;);<br>    link.download = filename;<br>    link.click();<br>    URL.revokeObjectURL(url);<br>  };<br>  img.src = url;<br>}<br>&lt;/script&gt;<br>&lt;style&gt;<br>...</pre><p><strong>Why does this work?</strong> 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.</p><h3>Step 7. Styling</h3><p>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.</p><p>📄 <strong>app.vue</strong></p><pre>...<br><br>&lt;style&gt;<br>.banner-here svg {<br>  width: 100%;<br>  height: 100%;<br>}<br>&lt;/style&gt;</pre><p>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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EiV3cbZTqXMrgA4E" /></figure><p>By now, your project should look something like this screenshot. Let’s run it to see the real magic!</p><h3>Running our code</h3><p>To see our app in action, run the command below and open <a href="http://localhost:3000/">http://localhost:3000</a> on your browser.</p><p>For reference, here is the <a href="https://github.com/Code-Pop/build-a-greeting-card-maker-with-vue-3-and-satori">Github repo for this tutorial</a>.</p><pre>yarn dev</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/656/0*txocqIkxj4QrLfFY" /></figure><p>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.</p><h3>Wrapping Up</h3><p>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.</p><p>Some ideas to expand your project:</p><ul><li>Add more background themes</li><li>Include text color/font selection</li><li>Create templates for birthdays, anniversaries</li></ul><p>Want to do more on the client side? Discover how to render 3d objects in the browser by reading <a href="https://www.vuemastery.com/blog/building-a-3d-scene-in-nuxt-with-tresjs/">this article</a>.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/build-a-greeting-card-maker-w-vue-3-satori"><em>https://www.vuemastery.com</em></a><em> on December 19, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c2ef115489c2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/build-a-greeting-card-maker-w-vue-3-satori-c2ef115489c2">Build a Greeting Card Maker w/ Vue 3 + Satori</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What’s next for Vue in 2025?]]></title>
            <link>https://medium.com/vue-mastery/whats-next-for-vue-in-2025-7996d2736e79?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/7996d2736e79</guid>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Thu, 19 Dec 2024 18:14:11 GMT</pubDate>
            <atom:updated>2024-12-19T18:14:11.058Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Vue in 2025" src="https://cdn-images-1.medium.com/max/1024/1*dGsHxAaeTZq_ltgY0D-Kxg.jpeg" /></figure><p><em>Written by Andy Li</em></p><p>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.</p><p>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.</p><p>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!</p><h3>Nuxt 4</h3><p>While Nuxt v4 doesn’t have a confirmed release date yet, you can already <a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now">try out its features</a>!</p><p>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:</p><p>📄 <strong>/nuxt.config.js</strong></p><pre>export default defineNuxtConfig({<br>  future: {<br>    compatibilityVersion: 4<br>  },<br>  ...<br>})</pre><p>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 <a href="https://www.vuemastery.com/blog/upgrading-to-nuxt-4">Upgrading to Nuxt 4</a>.</p><p>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 <a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now">Nuxt 4 features you can use now</a>.</p><h3>Vite 6</h3><p>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.</p><p>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.</p><p>If you want to learn more about Vite and Rolldown, you can check out <a href="https://www.vuemastery.com/blog/the-build-process-of-a-vue-app-rollup-vs-rolldown">the Build Process of a Vue app: Rollup vs Rolldown article</a>.</p><h3>Vitest 3</h3><p>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.”</p><p>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.</p><h3>Pinia 3</h3><p><a href="https://www.vuemastery.com/courses/pinia-fundamentals/fundamentals-what-is-pinia">Pinia</a> 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.</p><p>You still define a store with defineStore:</p><pre>import { defineStore } from &#39;pinia&#39;<br><br>export const useSampleStore = defineStore({})</pre><p>You can still set your state, getters, and actions the same way:</p><pre>export const useSampleStore = defineStore(&#39;sample&#39;, {<br>  state() {<br>    return { text: &#39;&#39; }<br>  },<br>  getters: {<br>    uppercase(state) {<br>      return state.text.toUpperCase()<br>    }<br>  },<br>  actions: {<br>    setText(val) {<br>      this.text = val <br>    }<br>  }<br>}</pre><p>And you can still use the Composition API style syntax to do the same thing:</p><pre>export const useSampleStore = defineStore(&#39;sample&#39;, () =&gt; {<br>  const text = ref(&#39;&#39;)<br><br>const uppercase = computed(() =&gt; {<br>    return text.value.toUpperCase()<br>  })<br>  const setText = (val) =&gt; {<br>    text.value = val<br>  }<br>  return { text, uppercase, setText }<br>})</pre><p>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.</p><h3>Vapor Mode</h3><p>Vue’s Vapor mode is still under research and development.</p><p>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 <em>diffing</em> to figure out what has to be updated in the actual DOM.</p><p>Vapor Mode is designed to eliminate this Virtual DOM creation and the diffing steps, in order to make the reactive process faster.</p><p>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.</p><p>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.</p><p>Although Vapor Mode is still in development, you can experiment with it through the <a href="https://github.com/vuejs/vue-vapor">vue-vapor repo</a>. 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.</p><p>On Vue Mastery, you can soon check out our Vapor Mode course taught by Vue Creator Evan You!</p><h3>What’s Next for Vue in 2025</h3><p>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.</p><p>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 <a href="https://www.vuemastery.com/pricing/?coupon=HOLIDAY-24">get an entire year’s subscription for 50% off</a><strong>!</strong> Become the best Vue developer you can be in 2025.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/whats-next-for-vue-in-2025"><em>https://www.vuemastery.com</em></a><em> on December 18, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7996d2736e79" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/whats-next-for-vue-in-2025-7996d2736e79">What’s next for Vue in 2025?</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Build Process of a Vue App: Rollup vs Rolldown]]></title>
            <link>https://medium.com/vue-mastery/the-build-process-of-a-vue-app-rollup-vs-rolldown-38b196e17ae1?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/38b196e17ae1</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[vitejs]]></category>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Wed, 04 Dec 2024 19:16:51 GMT</pubDate>
            <atom:updated>2024-12-04T19:16:51.548Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="The Build Process of a Vue App: Rollup vs Rolldown" src="https://cdn-images-1.medium.com/max/1024/1*wdnzqp1BdNic9Twh5QKhaA.png" /></figure><p><em>Written by Andy Li</em></p><p>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.</p><p>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 <em>Rolldown</em>.</p><p>First, let’s start with Vite.</p><h3>Vite’s Current Build Process</h3><p>Like many other frameworks, Vue.js is using Vite as its build tool.</p><p>Vite has two different modes: development and production. They’re implemented differently behind the scene.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/998/0*7xGK7uLVbBWYxcjH" /></figure><p>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.</p><p>Let’s explore how Vite handles development builds, where its innovative approach really shines.</p><h3>Vite in Development Mode</h3><p>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;script&gt; code), allowing for rapid browser updates with minimal processing.</p><p>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.</p><p>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.</p><h3>Vite in Production Mode</h3><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>💡 <strong>So to summarize:</strong> Vite uses esbuild (optimized for speed) for development builds and Rollup (known for its plugins) for production builds.</p><p>While this setup has worked well, a major change is coming: Vite will soon replace both esbuild <strong>and</strong> Rollup with a new tool called <em>Rolldown</em>.</p><p>Before exploring Rolldown, let’s examine the challenges with Vite’s current setup that led to Rolldown’s invention.</p><h3>The Problems with Vite</h3><p>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.</p><p>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.</p><p>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.</p><p>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.</p><h3>Introduction to Rolldown</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*SQMdrHN-Ibn4Hh9w" /></figure><p>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.</p><p>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.</p><p>In essence, you’ll be able to use Vite the same way you do now, just with improved performance.</p><p><em>So how much faster are we talking?</em> If speed is the main selling point, it needs to be significantly faster to justify adopting a new tool, right?</p><p>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.</p><p>This benchmark specifically measures production build time. <em>What about development?</em></p><p>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.</p><p>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).</p><p>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.</p><p>Oxc tools are generally faster than other similar tools because they were written from scratch with performance as their primary goal.</p><h3>The Future of Vite</h3><p>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.</p><p>If you’d like to learn more about Vite, check out the course <a href="https://www.vuemastery.com/courses/lightning-fast-builds-with-vite/intro-to-vite">Lightning Fast Builds</a> here on Vue Mastery, taught by Vite’s creator Evan You himself.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/the-build-process-of-a-vue-app-rollup-vs-rolldown"><em>https://www.vuemastery.com</em></a><em> on December 4, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=38b196e17ae1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/the-build-process-of-a-vue-app-rollup-vs-rolldown-38b196e17ae1">The Build Process of a Vue App: Rollup vs Rolldown</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nuxt 4 features you can use now]]></title>
            <link>https://medium.com/vue-mastery/nuxt-4-features-you-can-use-now-7dd0bd89cf92?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/7dd0bd89cf92</guid>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[nuxt]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Thu, 12 Sep 2024 21:23:52 GMT</pubDate>
            <atom:updated>2024-09-12T21:23:52.623Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Nuxt 4 Features you can use now" src="https://cdn-images-1.medium.com/max/1024/1*6vkN6UlRSWLY0m2--hrLFQ.jpeg" /></figure><p><em>Written by Andy Li</em></p><p>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, <em>route groups</em> and <em>custom prefetch trigger</em>.</p><h3>Nuxt Route Groups</h3><p>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.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lShtmAgiFUk86cSJ" /></figure><p>But if you put all the files on the same level like this, it could be difficult to see how they’re related.</p><p>Since <strong>about</strong> and <strong>contact</strong> are information-related while <strong>signup</strong> and <strong>login</strong> are authentication-related, the file structure would be more expressive if they are grouped like this with folders:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ppa9G3YEI1llTCsf" /></figure><p>But the problem with this is that you might not want the extra prefix on the URL path.</p><p>The solution for this problem is called Route Groups. With the above example, the folders just have to be named with parentheses:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hkNWra2V8iU5VuEE" /></figure><p>Now all these pages can still be accessed the same way. Basically, Nuxt just ignores the folders named with parentheses.</p><p>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.</p><h3>Custom Prefetch Triggers</h3><p>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.</p><p>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.</p><p>In Nuxt.js, prefetching is enabled by default if you use &lt;NuxtLink&gt;:</p><pre>&lt;NuxtLink href=&quot;/signup&quot;&gt;signup&lt;/NuxtLink&gt;</pre><p>Here, the signup page will now be prefetched when this link is visible on screen the first time.</p><p>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.</p><p>This is very wasteful. So, <em>when to trigger the prefetch</em> becomes the key.</p><p>Nuxt now offers two triggers for prefetching:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*84yWYaNBqIurOdwM" /></figure><p>The <em>visibility</em> trigger is the default configuration.</p><p>You can set the <em>interaction</em> trigger like this:</p><pre>&lt;NuxtLink href=&quot;/signup&quot; prefetch-on=&quot;interaction&quot;&gt;signup&lt;/NuxtLink&gt;</pre><p>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.</p><p>In practice, you’ll likely have some links you want to prefetch by <em>visibility</em> and others by <em>interaction</em>. But as an option, you can set one trigger for the entire app in the config file:</p><p>📄 <strong>/nuxt.config.ts</strong></p><pre>export default defineNuxtConfig({<br>  experimental: {<br>    defaults: {<br>      nuxtLinks: {<br>        prefetchOn: {<br>          visibility: false,<br>          interaction: true<br>        }<br>      }<br>    }<br>  }<br>})</pre><p>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.</p><p>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.</p><h3>Other Features</h3><p>We’ve only covered two of the more important features from Nuxt v3.13. You can find the full list of features in <a href="https://nuxt.com/blog/v3-13">the official Nuxt Blog</a>.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/nuxt-4-features-you-can-use-now/"><em>https://www.vuemastery.com</em></a><em> on September 12, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7dd0bd89cf92" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/nuxt-4-features-you-can-use-now-7dd0bd89cf92">Nuxt 4 features you can use now</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamic Layouts with Vue jsx: A Guide to Flexible and Maintainable UIs]]></title>
            <link>https://medium.com/vue-mastery/dynamic-layouts-with-vue-jsx-a-guide-to-flexible-and-maintainable-uis-76ee06f7ba57?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/76ee06f7ba57</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[developer-tools]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Fri, 06 Sep 2024 17:05:35 GMT</pubDate>
            <atom:updated>2024-09-06T17:04:44.098Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Dynamic Layouts with Vue jsx" src="https://cdn-images-1.medium.com/max/1024/1*lBllAW9fZBFwMohQJPoTZw.png" /></figure><p><em>Written by </em><a href="https://x.com/dubem_design"><em>Dubem Izuorah</em></a></p><p>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.</p><h3>Introducing Dynamic Layouts</h3><p>By creating layouts at runtime based on data, you can build flexible, maintainable, and scalable applications that adapt effortlessly to changes. This approach,called <strong>Dynamic layouts</strong>, eliminates the need for repetitive HTML edits, allowing your UI to stay in sync with your data seamlessly.</p><p>In this article, we’ll explore how dynamic layouts can transform your development workflow and elevate your projects.</p><h3>Why Use Dynamic Layouts?</h3><p>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.</p><h3>Benefits of Dynamic Layouts</h3><ul><li><strong>Flexibility</strong>: Easily adjust UI components based on underlying data without modifying HTML or template code.</li><li><strong>Maintainability</strong>: Adhere to the DRY (Don’t Repeat Yourself) principle, reducing repetitive code and simplifying maintenance.</li><li><strong>Scalability</strong>: Manage your UI programmatically, allowing for easy updates, additions, or removals of items.</li><li><strong>Error Reduction</strong>: Minimize manual HTML edits, decreasing the likelihood of errors.</li><li><strong>Enhanced Responsiveness</strong>: Dynamically show or hide UI elements based on conditions, making the interface more responsive to user interactions or data changes.</li></ul><h3>Practical Scenarios for Dynamic Layouts</h3><p>Dynamic layouts shine in various real-world scenarios, such as:</p><ul><li><strong>Forms</strong>: 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.</li><li><strong>Blog Post Lists</strong>: Manage and update each post card’s content dynamically based on its data.</li><li><strong>Content Blocks</strong>: Organize and display diverse content elements efficiently.</li><li><strong>User Lists, Navigation Items, Dashboard Panels</strong>: Ensure consistent styling and easy adjustments without manual HTML updates.</li><li><strong>Website Sections</strong>: Dynamically generate sections to maintain a cohesive and easily adjustable layout.</li></ul><p>By generating these elements dynamically, you ensure consistency, simplify adjustments, and maintain a clean codebase.</p><h3>Building a Dynamic Navbar</h3><p>Let’s gain hands-on experience with dynamic layouts by building a dynamic navbar. For reference, here is the <a href="https://github.com/Code-Pop/Dynamic-Layouts">Github repo for this tutorial.</a></p><h3>Requirements</h3><p>Imagine you’re developing a payment platform and need to create a clean, extensible navbar component as shown in the screenshots below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gtVp1TKqACbv3318" /></figure><p><em>Navbar when a user is logged in</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vxQBAJ24KQW37ShT" /></figure><p><em>Navbar with no user logged in</em></p><h3>Setting up the Environment</h3><p>To focus on dynamic layouts, we won’t delve into environment setup details here. You can find the complete code samples in our <a href="https://github.com/Code-Pop/Dynamic-Layouts">GitHub repository</a>.</p><p><strong>Technologies Used:</strong></p><ol><li><a href="https://nuxt.com/">Nuxt</a> — Vue framework</li><li><a href="https://tailwindcss.nuxtjs.org/">Nuxt TailwindCSS</a> — Styling</li><li><a href="https://github.com/phosphor-icons/vue">Phosphor-icons Vue</a> — Icon components</li></ol><h3>Static Approach</h3><p>A static Navbar component involves numerous HTML tags and properties, making the code hard to read and maintain.</p><p>Updating such a layout is error-prone, especially with repetitive elements sharing the same properties or styling.</p><p>While a static approach is useful for drafting layouts, dynamic layouts are preferable for maintainability and collaboration.</p><p><strong>Example of a Static Navbar:</strong></p><p>📄 <strong>App.vue</strong></p><pre>&lt;template&gt;<br>  &lt;nav class=&quot;w-screen border-b py-4&quot;&gt;<br>    &lt;div class=&quot;container mx-auto flex items-center gap-10&quot;&gt;<br>      &lt;!-- Logo --&gt;<br>      &lt;NuxtLink href=&quot;/&quot; class=&quot;flex items-center&quot;&gt;<br>        &lt;img src=&quot;https://logo.clearbit.com/google.com&quot; class=&quot;w-10 h-10 object-contain&quot; /&gt;<br>      &lt;/NuxtLink&gt;<br><br>&lt;!-- Dashboard or Home Link --&gt;<br>      &lt;NuxtLink v-if=&quot;user&quot; href=&quot;/dashboard&quot; class=&quot;flex items-center&quot;&gt;<br>        &lt;PhGauge size=&quot;24&quot; /&gt;<br>        &lt;span class=&quot;ml-2&quot;&gt;Dashboard&lt;/span&gt;<br>      &lt;/NuxtLink&gt;<br>      &lt;NuxtLink v-else href=&quot;/home&quot; class=&quot;flex items-center&quot;&gt;<br>        &lt;PhHouseSimple size=&quot;24&quot; /&gt;<br>        &lt;span class=&quot;ml-2&quot;&gt;Home&lt;/span&gt;<br>      &lt;/NuxtLink&gt;<br>      &lt;!-- Spacer --&gt;<br>      &lt;div class=&quot;ml-auto&quot;&gt;&lt;/div&gt;<br>      &lt;!-- Add Funds Button (Visible only when user is authenticated) --&gt;<br>      &lt;button v-if=&quot;user&quot; class=&quot;flex items-center&quot;&gt;<br>        &lt;span class=&quot;text-white bg-green-500 px-4 py-2 rounded-lg&quot;&gt;Add Funds&lt;/span&gt;<br>      &lt;/button&gt;<br>      &lt;!-- User Avatar (Visible only when user is authenticated) --&gt;<br>      &lt;div v-if=&quot;user&quot; class=&quot;flex items-center&quot;&gt;<br>        &lt;Avatar src=&quot;https://randomuser.me/api/portraits/men/40.jpg&quot; /&gt;<br>        &lt;span class=&quot;ml-2&quot;&gt;Dubem Izuorah&lt;/span&gt;<br>      &lt;/div&gt;<br>      &lt;!-- Auth Button --&gt;<br>      &lt;button class=&quot;flex items-center&quot; @click=&quot;user = !user&quot;&gt;<br>        &lt;span&gt;{{ user ? &#39;Logout&#39; : &#39;Login&#39; }}&lt;/span&gt;<br>      &lt;/button&gt;<br>    &lt;/div&gt;<br>  &lt;/nav&gt;<br>  &lt;main class=&quot;h-64 bg-gray-100 flex justify-center pt-10 text-xl&quot;&gt;App Here&lt;/main&gt;<br>&lt;/template&gt;<br>&lt;script setup lang=&quot;jsx&quot;&gt;<br>import { ref } from &quot;vue&quot;;<br>import { NuxtLink, Avatar } from &quot;#components&quot;;<br>import { PhGauge, PhHouseSimple } from &quot;@phosphor-icons/vue&quot;;<br>// Simulate user authentication status<br>const user = ref(true);<br>&lt;/script&gt;</pre><p>While static layouts may suffice for simple interfaces, dynamic layouts offer superior maintainability and scalability.</p><h3>Dynamic Approach</h3><p>To create a dynamic navbar, we’ll want to define our layout data and generate UI components based on this data.</p><h3>Defining the Data</h3><p>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.</p><p>📄 <strong>App.vue</strong></p><pre>&lt;script setup lang=&quot;jsx&quot;&gt;<br>// Import necessary components<br>import { ref, computed } from &quot;vue&quot;;<br>import { NuxtLink, Avatar } from &quot;#components&quot;;<br>import { PhGauge, PhHouseSimple } from &quot;@phosphor-icons/vue&quot;;<br><br>// Simulate user authentication status<br>const user = ref(true);<br>// Define menu items<br>const items = computed(() =&gt; [<br>  {<br>    key: &quot;logo&quot;,<br>    image: &quot;https://logo.clearbit.com/google.com&quot;,<br>    href: &quot;/&quot;<br>  },<br>  {<br>    icon: user.value ? PhGauge : PhHouseSimple,<br>    key: &quot;dashboard&quot;,<br>    title: user.value ? &quot;Dashboard&quot; : &quot;Home&quot;,<br>    to: user.value ? &quot;/dashboard&quot; : &quot;/home&quot;,<br>  },<br>  {<br>    key: &quot;spacer&quot;,<br>    class: &quot;ml-auto&quot;,<br>  },<br>  {<br>    key: &quot;add-funds&quot;,<br>    render: () =&gt; &lt;span class=&quot;text-white bg-green-500 px-4 py-2 rounded-lg&quot;&gt;Add Funds&lt;/span&gt;<br>  },<br>  {<br>    key: &quot;user&quot;,<br>    render: () =&gt; &lt;Avatar src=&quot;https://randomuser.me/api/portraits/men/40.jpg&quot; /&gt;,<br>    title: &quot;Dubem Izuorah&quot;,<br>    hide: !user.value<br>  },<br>  {<br>    key: &quot;auth&quot;,<br>    title: user.value ? &quot;Logout&quot; : &quot;Login&quot;,<br>    onClick: () =&gt; user.value = !user.value<br>  },<br>]);<br>// Filter out hidden items<br>const menuItems = computed(() =&gt; items.value.filter(item =&gt; !item.hide));<br>&lt;/script&gt;<br></pre><p><strong>Key Takeaways:</strong></p><ol><li><strong>Reactive Layout</strong>: Use computed properties to update the layout based on reactive data like user.</li><li><strong>Conditional Rendering</strong>: Apply different values using ternary operators based on state.</li><li><strong>JSX Integration</strong>: Utilize JSX to include HTML elements and Vue components directly within property values.</li><li><strong>Dynamic Properties</strong>: Use properties like hide to conditionally display items.</li><li><strong>Event Handling</strong>: Store functions or event handlers (e.g., onClick) within data objects.</li><li><strong>Modular Data Management</strong>: Move layout data to Vue composables or external JSON files for better organization.</li></ol><h3>Defining the Avatar Component</h3><p>Create an Avatar component used within the dynamic navbar.</p><p>📄 <strong>Avatar.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div class=&quot;rounded-full w-10 h-10 bg-gray-100 overflow-hidden&quot;&gt;<br>    &lt;img class=&quot;w-full h-full object-cover&quot; :src=&quot;src&quot; /&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>const props = defineProps({<br>  src: {<br>    type: String,<br>    required: true,<br>  },<br>})<br>&lt;/script&gt;</pre><p>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.</p><h3>Building the Layout</h3><p>Use the defined data to render the navbar dynamically.</p><p>📄 <strong>App.vue</strong></p><pre>&lt;template&gt;<br>  &lt;nav class=&quot;w-screen border-b py-4&quot;&gt;<br>    &lt;div class=&quot;container mx-auto flex items-center gap-10&quot;&gt;<br>      &lt;component<br>        v-for=&quot;item in menuItems&quot;<br>        :key=&quot;item.key&quot;<br>        :is=&quot;item.to ? NuxtLink : &#39;button&#39;&quot;<br>        v-bind=&quot;item&quot;<br>        @click=&quot;item.onClick&quot;<br>        class=&quot;flex items-center&quot;<br>      &gt;<br>        &lt;img v-if=&quot;item.image&quot; :src=&quot;item.image&quot; class=&quot;w-10 h-10 object-contain&quot; /&gt;<br>        &lt;component v-if=&quot;item.render&quot; :is=&quot;item.render&quot; /&gt;<br>        &lt;component v-if=&quot;item.icon&quot; :is=&quot;item.icon&quot; :size=&quot;24&quot; /&gt;<br>        &lt;span v-if=&quot;item.title&quot; class=&quot;ml-2&quot;&gt;{{ item.title }}&lt;/span&gt;<br>      &lt;/component&gt;<br>    &lt;/div&gt;<br>  &lt;/nav&gt;<br>  &lt;main class=&quot;h-64 bg-gray-100 flex justify-center pt-10 text-xl&quot;&gt;App Here&lt;/main&gt;<br>&lt;/template&gt;<br><br>&lt;script setup lang=&quot;jsx&quot;&gt;<br>// The script remains the same as above<br>&lt;/script&gt;</pre><p><strong>Key Takeaways:</strong></p><ol><li><strong>v-bind Usage</strong>: Attach multiple properties to elements simultaneously, keeping the template clean.</li><li><strong>Unique Keys</strong>: Use the key attribute to prevent rendering issues during dynamic updates.</li><li><strong>Dynamic Components</strong>: Utilize Vue’s dynamic components to render different elements based on data.</li><li><strong>Event Binding</strong>: Attach events like @click dynamically based on data properties.</li><li><strong>Component Modularity</strong>: Break down components further and import them as needed for better scalability.</li></ol><p>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.</p><h3>Enhancing Flexibility with Vue JSX</h3><p><a href="https://vuejs.org/guide/extras/render-function">Vue JSX</a> 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.</p><p>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.</p><p>In our solution, JSX helps simplify how we dynamically render components like the Avatar or conditional elements such as the “Add Funds” button.</p><p>For example, instead of hardcoding these elements into a template, we can dynamically generate them using a render function in JavaScript.</p><p>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.</p><p>By using JSX, we maintain cleaner, more maintainable code, while still leveraging Vue’s reactivity and flexibility.</p><h3>Next Steps</h3><p>Now that we understand dynamic layouts, it’s time to put your knowledge into practice. Here are some suggested next steps:</p><ol><li><strong>Refactor Existing Projects</strong>: 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.</li><li><strong>Integrate with CMS</strong>: Experiment with integrating a CMS or other systems with your dynamic layouts to create even more dynamic and responsive user interfaces.</li><li><strong>Collaborate with Your Team</strong>: 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.</li><li><strong>Explore Advanced Use Cases</strong>: Continue exploring more advanced applications of dynamic layouts. Mastery comes from consistent practice and exploration.</li></ol><p>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 <a href="https://www.vuemastery.com/courses/easy-interfaces-with-vuetify/intro-to-vuetify">Easy Interfaces with Vuetify</a>, the popular component library for Vue.js developers.</p><p>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.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/dynamic-layouts-with-vue-jsx"><em>https://www.vuemastery.com</em></a><em> on September 5, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=76ee06f7ba57" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/dynamic-layouts-with-vue-jsx-a-guide-to-flexible-and-maintainable-uis-76ee06f7ba57">Dynamic Layouts with Vue jsx: A Guide to Flexible and Maintainable UIs</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Minimalist Nuxt Authentication]]></title>
            <link>https://medium.com/vue-mastery/minimalist-nuxt-authentication-8de8caac4117?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/8de8caac4117</guid>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[nuxt]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Mon, 15 Jul 2024 18:07:55 GMT</pubDate>
            <atom:updated>2024-08-05T16:51:06.869Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Minimalist Nuxt Authentication" src="https://cdn-images-1.medium.com/max/1024/1*OsOcaoKo8NYdVnG7FmYxEw.png" /></figure><p><em>Written by </em><a href="https://timiomoyeni.com/"><em>Timi Omoyeni</em></a></p><p>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.</p><p>The Nuxt team understands the importance of Authentication which is why with Nuxt 2, we had the <a href="https://auth.nuxtjs.org/">Auth Module</a>. 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).</p><p>Since the introduction of Nuxt 3, there have been 3rd party plugins like the <a href="https://github.com/sidebase/nuxt-auth">Sidebase Nuxt Auth</a>, <a href="https://github.com/Hebilicious/authjs-nuxt">Authjs Nuxt</a> based on Auth.js that have made the authentication process easier for developers.</p><p>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 <a href="https://github.com/Timibadass/nuxt-auth-demo">GitHub</a>.</p><h3>Introduction</h3><p>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.</p><p>With Nuxt Auth Utils, you automatically get access to data like loggedIn, user, and session, and the clear method available in the useUserSession() composable. These properties return updated information about the current user’s session and can be accessed anywhere in your app.</p><p>This is similar to how we use the $auth property to access user information in <a href="https://auth.nuxtjs.org/api/auth">Nuxt 2</a>, the difference now is that Nuxt 3 takes advantage of Vue Composables introduced in Vue 3.</p><p>Creating a personalized profile header with the current user’s name can be done like this:</p><pre>&lt;template&gt;<br>  &lt;header&gt;<br>    &lt;template v-if=&quot;loggedIn&quot;&gt;<br>      &lt;p&gt;<br>        Hi<br>        {{ user?.username }}<br>      &lt;/p&gt;<br>      &lt;button @click=&quot;logout&quot;&gt;Log out&lt;/button&gt;<br>    &lt;/template&gt;<br>    &lt;p v-else&gt;Guest&lt;/p&gt;<br>  &lt;/header&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>const { loggedIn, user, clear } = useUserSession();<br>const logout = () =&gt; {<br>  clear();<br>};<br>&lt;/script&gt;</pre><p>Using server utils, Nuxt auth utils automatically imports some helpers into the server/ directory that helps with session management. They include:</p><ol><li>setUserSession: This function accepts two arguments, event, and an object. Inside this object, we are expected to pass a user object along with any extra information we want to save with the session.</li><li>replaceUserSession: This function behaves similarly to the setUserSession but as the name implies, it replaces the current session data with the new user data that is passed.</li><li>getUserSession: This function returns information about the current user session. This information is the same as the object passed to setUserSession and replaceUserSession.</li><li>clearUserSession: This function is used to clear a user’s session.</li><li>requireUserSession: This function works like a middleware that checks for a user session and returns it if present otherwise it returns a 401 error.</li></ol><pre>await setUserSession(event, {<br>  user: {<br>    // ... user data<br>  },<br>  loggedInAt: new Date()<br>  // Any extra fields<br>})<br><br>// Replace a user session. Same behaviour as setUserSession, except it does not merge data with existing data<br>await replaceUserSession(event, data)<br>// Get the current user session<br>const session = await getUserSession(event)<br>// Clear the current user session<br>await clearUserSession(event)<br>// Require a user session (send back 401 if no `user` key in session)<br>const session = await requireUserSession(event)</pre><p>Note that this module requires the Nuxt server to be running as it uses the server API route hence, it cannot run with nuxt generate.</p><h3>Nuxt Auth Utils Features</h3><h3>Hybrid Rendering</h3><p>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.</p><p>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.</p><p>As a solution to this, we use the &lt;[AuthState](https://github.com/Atinux/nuxt-auth-utils/blob/c8b02d0b84a53ab4dd41f1808d9365d1c52c8366/src/runtime/app/components/AuthState.vue)&gt; component to safely display auth-related data without worrying about the rendering mode.</p><pre>&lt;template&gt;<br>  &lt;header&gt;<br>    &lt;AuthState v-slot=&quot;{ loggedIn, clear }&quot;&gt;<br>      &lt;button v-if=&quot;loggedIn&quot; @click=&quot;clear&quot;&gt;Logout&lt;/button&gt;<br>      &lt;NuxtLink v-else to=&quot;/login&quot;&gt;Login&lt;/NuxtLink&gt;<br>    &lt;/AuthState&gt;<br>  &lt;/header&gt;<br>&lt;/template&gt;</pre><p>This component also offers support for a loading state that comes in handy while the user session is being fetched on the client side.</p><h3>OAuth Providers</h3><p>Other than the native email/username and password authentication flow, Nuxt Auth Utils also <a href="https://github.com/atinux/nuxt-auth-utils/tree/main?tab=readme-ov-file#supported-oauth-providers">offers support</a> for authentication with third-party services using OAuth. It also comes with event handlers that are exposed from the oauth global variable that is available in your server routes (server/ directory). Some of the supported providers include:</p><ol><li>Google</li><li>GitHub</li><li>Auth0</li><li>Facebook</li><li>LinkedIn.</li></ol><h3>Extendable with Hooks</h3><p>This module also provides hooks that let you perform extra actions when a session is fetched or updated. This can be achieved by using <a href="https://github.com/Atinux/nuxt-auth-utils/blob/c8b02d0b84a53ab4dd41f1808d9365d1c52c8366/src/runtime/server/utils/session.ts#L8">sessionHook.hook</a> method, which accepts a hook string (’fetch’ or ‘clear’).</p><pre>export default defineNitroPlugin(() =&gt; {<br>  // Called when the session is fetched during SSR for the Vue composable (/api/_auth/session)<br>  // Or when we call useUserSession().fetch()<br>  sessionHooks.hook(&#39;fetch&#39;, async (session, event) =&gt; {<br>    // extend User Session by calling your database<br>    // or<br>    // throw createError({ ... }) if session is invalid for example<br>  })<br><br>// Called when we call useServerSession().clear() or clearUserSession(event)<br>  sessionHooks.hook(&#39;clear&#39;, async (session, event) =&gt; {<br>    // Log that user logged out<br>  })<br>})</pre><p>This method is available anywhere in the server/ directory.</p><h3>Getting Started with Nuxt Auth Utils</h3><p>To fully understand how Nuxt Auth Utils work, we will build a simple Nuxt app that uses an SQL server.</p><h3>Setup</h3><p>First, we create our Nuxt project using the npx command;</p><pre>npx nuxi@latest init nuxt-auth-demo</pre><p>To use the auth utils in your Nuxt project, you need to install it using the following command:</p><pre>npx nuxi@latest module add auth-utils</pre><p>Once this installation is completed, our nuxt.config.ts file gets updated with the following lines of code:</p><p><strong>📄 nuxt.config.ts</strong></p><pre>// https://nuxt.com/docs/api/configuration/nuxt-config<br>export default defineNuxtConfig({<br>  devtools: { enabled: true },<br>  modules: [&quot;nuxt-auth-utils&quot;] // added automatically<br>})</pre><p>Here, nuxt-auth-utils is added to the modules 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 <strong>NUXT_SESSION_PASSWORD</strong> to our .env file. This step is optional in development as the plugin automatically generates one if it isn’t set.</p><h3>Building The Server</h3><p>Now that the configuration is complete, we will build our server. For this, we will use a simple SQLite server that requires username and password for both signup and login.</p><p>The first thing we need to do is create a server/ directory in the root folder of our project. This directory will contain folders for both our database configuration and authentication files.</p><p>Here’s what our project structure will look like:</p><pre>nuxt-auth-demo/<br>│<br>├── server/<br>│   ├── api/<br>│   │   ├── auth/<br>│   │   │   ├── signup.js<br>│   │   │   ├── login.js<br>│   ├── db/<br>│   │   ├── database.js<br>│<br>├── other-project-files</pre><p>Before we can set up our database, we need to install the following packages in our project:</p><ol><li>sqlite: This is the SQLite library for Node.js, which allows us to interact with SQLite databases.</li><li>sqlite3: This is the SQLite3 database driver for Node.js, used by the sqlite library.</li><li>bcrypt: This is a library to help us hash passwords, ensuring that user passwords are stored securely.</li></ol><p>You can install these packages using the following command:</p><pre>yarn add sqlite sqlite3 bcrypt</pre><p>After installing, we create a database.js file and add the following:</p><p><strong>📂 server/db/database.js</strong></p><pre>import { open } from &quot;sqlite&quot;;<br>import sqlite3 from &quot;sqlite3&quot;;<br><br>export const initDb = async () =&gt; {<br>  try {<br>    const db = await open({<br>      filename: &quot;./database.sqlite&quot;,<br>      driver: sqlite3.Database,<br>    });<br>    await db.exec(`<br>      CREATE TABLE IF NOT EXISTS users (<br>        id INTEGER PRIMARY KEY AUTOINCREMENT,<br>        username TEXT UNIQUE,<br>        password TEXT<br>      )<br>    `);<br>    console.log(&quot;Database initialized successfully&quot;);<br>    return db;<br>  } catch (error) {<br>    console.error(&quot;Failed to initialize database:&quot;, error);<br>    throw error;<br>  }<br>};</pre><p>This file establishes and initializes the SQLite database, makes sure the required tables are there, and offers a connection method.</p><p>Firstly, we import open function which is used to open and connect to the db, and sqlite3, which is the SQLite database driver. We export initDb function, which will open and connect to the db.</p><p>In this function, we call the open function and pass an object:</p><pre>{<br>    filename: &quot;./database.sqlite&quot;,<br>    driver: sqlite3.Database,<br>}</pre><p>This object contains filename, which accepts the file path to our database file.</p><p>This file is automatically created the first time initDb is run and it uses the file path passed to this field to create this value. This means “./database.sqlite” will be created in the root folder of our project while &quot;./server/db/database.sqlite&quot; will be created inside the db/ directory.</p><p>We also have a driver property that specifies which database to use (sqlite3.database).</p><p>The open function resolves a promise, so we wrap it in a try/catch block. If the connection is successful, we execute an SQL command db.exec, which creates a users table if it does not exist in the db with each row having columns for a unique id, and username, and password.</p><p>Still, if it fails, we throw an error that says Failed to initialize database with the specific error.</p><p>Finally, we export this initDb function to reference our db from our auth files.</p><p>After setting up our database, we will create an api folder inside the server/ directory. Within this folder, we will create an auth folder, which will contain both the signup.js and login.js files.</p><p>Let us start with signup.js:</p><p><strong>📂 server/api/auth/signup.js</strong></p><pre>import bcrypt from &quot;bcrypt&quot;;<br>import { initDb } from &quot;../../db/database&quot;;<br><br>export default defineEventHandler(async (event) =&gt; {<br>  try {<br>    const body = await readBody(event); // Retrieve request body<br>    if (!body) {<br>      return { error: &quot;Request body is empty or undefined&quot; };<br>    }<br>    const { username, password } = body;<br>    if (!username || !password) {<br>      return { error: &quot;Username and password are required&quot; };<br>    }<br>    const db = await initDb(); // Initialize database connection<br>    const hashedPassword = await bcrypt.hash(password, 10); // Hash password<br>    try {<br>      // Insert user data into database<br>      await db.run(&quot;INSERT INTO users (username, password) VALUES (?, ?)&quot;, [<br>        username,<br>        hashedPassword,<br>      ]);<br>      const userData = { username: user.username };<br>      await setUserSession(event, {<br>        user: userData,<br>        loggedInAt: new Date(),<br>      });<br>      return { success: true, user };<br>    } catch (error) {<br>      console.error(&quot;Error creating user:&quot;, error);<br>      return createError({<br>        statusCode: 409,<br>        statusMessage: &quot;Username already exists&quot;,<br>      });<br>    }<br>  } catch (error) {<br>    console.error(&quot;Error handling signup request:&quot;, error);<br>    return createError({<br>      statusCode: 400,<br>      statusMessage: &quot;Failed to process request&quot;,<br>    });<br>  }<br>});</pre><p>Here, we use the <a href="https://nuxt.com/docs/guide/directory-structure/server">defineEventHandler</a> function to set up our signup API so Nuxt recognizes our API and makes it available under /api/auth/signup. In this function, we can access the body of this request (i.e username and password) by passing event to <a href="https://nuxt.com/docs/guide/directory-structure/server#body-handling">readBody</a>.</p><p>This function resolves a promise, so we must handle it within a try/catch block to properly manage potential errors.</p><p>On success, we get the request’s body and destructure it to extract username and password. If there’s an error, we use <a href="https://nuxt.com/docs/guide/directory-structure/server#error-handling">createError</a> for error handling. This ensures that any issues during the process are captured and reported appropriately.</p><p>Before we can proceed with storing this user data in the database, we need to hash the user’s password. We use the <a href="https://github.com/dcodeIO/bcrypt.js?tab=readme-ov-file#hashs-salt-callback-progresscallback">bcrypt.hash</a> method, which accepts two arguments: password and salt. The password is the user’s plain text password, and 10 is the salt length, determining the complexity of the hashing process.</p><p>If the provided data is valid, the signup process succeeds, and we fetch the newly created user information using db.get. This information is then passed to setUserSession with a loggedInAt property to store the time.</p><p>At this point, our signup endpoint is ready and we can test it using either the <a href="https://www.vuemastery.com/blog/exploring-the-nuxt-3-devtools">Nuxt DevTools</a> or by creating a signup.vue file in the pages/ directory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*66mstQ-qyNZ2svve" /></figure><p>When building out APIs in the server/ directory, Nuxt DevTools can automatically read the directory for valid APIs to display for easy testing. This is why we can see our api folder listed here but not the db folder. Using this approach, we can quickly test our API to ensure it works.</p><p>In our app, we’re going to create a form component, AuthForm.vue in our components/ directory which will look like this:</p><p><strong>📂 components/AuthForm.vue</strong></p><pre>&lt;template&gt;<br>  &lt;form @submit.prevent=&quot;submit&quot; class=&quot;auth__form&quot;&gt;<br>    &lt;h1 class=&quot;auth__heading&quot;&gt;{{title}}&lt;/h1&gt;<br>    &lt;div class=&quot;auth__div&quot;&gt;<br>      &lt;label for=&quot;username&quot; class=&quot;form__label&quot;&gt;Username&lt;/label&gt;<br>      &lt;input<br>        type=&quot;text&quot;<br>        name=&quot;username&quot;<br>        id=&quot;username&quot;<br>        required<br>        v-model=&quot;username&quot;<br>        class=&quot;form__input&quot;<br>      /&gt;<br>    &lt;/div&gt;<br>    &lt;div class=&quot;auth__div&quot;&gt;<br>      &lt;label for=&quot;password&quot; class=&quot;form__label&quot;&gt;Password&lt;/label&gt;<br>      &lt;input<br>        type=&quot;password&quot;<br>        name=&quot;password&quot;<br>        id=&quot;password&quot;<br>        required<br>        v-model=&quot;password&quot;<br>        class=&quot;form__input&quot;<br>      /&gt;<br>    &lt;/div&gt;<br>    &lt;button class=&quot;form__button&quot; :disabled=&quot;loading&quot;&gt;<br>      &lt;template v-if=&quot;loading&quot;&gt;please wait...&lt;/template&gt;<br>      &lt;template v-else&gt;{{title}}&lt;/template&gt;<br>    &lt;/button&gt;<br>  &lt;/form&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>const username = ref(&quot;&quot;);<br>const password = ref(&quot;&quot;);<br>defineProps({<br>  loading: {<br>    type: Boolean,<br>    default: false,<br>  },<br>  title: {<br>    type: String,<br>    required: true,<br>  },<br>});<br>const emit = defineEmits([&quot;submit&quot;]);<br>const submit = () =&gt; {<br>  const payload = {<br>    username: username.value,<br>    password: password.value,<br>  };<br>  emit(&quot;submit&quot;, payload);<br>};<br>&lt;/script&gt;<br>&lt;style lang=&quot;scss&quot; scoped&gt;<br>.auth {<br>  &amp;__form {<br>    border: 1px solid #e0e0e0;<br>    padding: 20px;<br>    border-radius: 8px;<br>  }<br>  &amp;__div {<br>    margin-bottom: 30px;<br>  }<br>}<br>.form {<br>  &amp;__label {<br>    display: block;<br>    margin-bottom: 4px;<br>    max-width: 300px;<br>    box-sizing: border-box;<br>  }<br>  &amp;__input {<br>    height: 50px;<br>    width: calc(100% - 20px);<br>    padding-left: 20px;<br>    border-radius: 8px;<br>    border: 1px solid #e0e0e0;<br>  }<br>  &amp;__button {<br>    height: 50px;<br>    border-radius: 8px;<br>    background-color: #008065;<br>    color: #fff;<br>    border: 0;<br>    width: 100%;<br>    cursor: pointer;<br>  }<br>}<br>&lt;/style&gt;</pre><p>In this component, we create a form that accepts username and password. When the form is submitted, the submit function emits the value obtained from the form to the parent component using the submit event.</p><p>We also define props using the defineProps method: title, and loading. Since we plan to use this form for signup and login, we use the title prop to make the form heading dynamic. We use the loading prop to indicate a network request is in progress and to disable the button during this time.</p><p>In pages/ directory, we create signup.vue with the following code:</p><p><strong>📂 pages/signup.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div class=&quot;signup&quot;&gt;<br>    &lt;AuthForm :loading=&quot;loading&quot; @submit=&quot;register&quot; title=&quot;Sign up&quot; /&gt;<br>    &lt;p class=&quot;signup__text&quot;&gt;<br>      Already registered?<br>      &lt;nuxt-link :to=&quot;{ name: &#39;login&#39; }&quot;&gt;Log in&lt;/nuxt-link&gt;<br>    &lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import AuthForm from &quot;@/components/AuthForm.vue&quot;;<br>const loading = ref(false);<br>const router = useRouter();<br>const register = async (body) =&gt; {<br>  loading.value = true;<br>  try {<br>    await $fetch(&quot;/api/auth/signup&quot;, {<br>      method: &quot;POST&quot;,<br>      body,<br>    });<br>    router.push({name: &#39;Dashboard&#39;})<br>    loading.value = false;<br>  } catch (error) {<br>    alert(error.statusMessage || error);<br>    loading.value = false;<br>  }<br>};<br>&lt;/script&gt;<br>&lt;style lang=&quot;scss&quot; scoped&gt;<br>.signup {<br>  width: 100%;<br>  padding: 50px;<br>  max-width: 400px;<br>  margin: auto;<br>  color: #333333;<br>  &amp;__text {<br>    text-align: right;<br>    &amp; a {<br>      text-decoration: underline;<br>      color: inherit;<br>    }<br>  }<br>}<br>&lt;/style&gt;</pre><p>Here, we import AuthForm.vue, provide the necessary props, and assign the register function to handle the submit event. When this form is submitted, we call our signup method using <a href="https://nuxt.com/docs/api/utils/dollarfetch">$fetch</a>, attaching the request body and method to the request.</p><p>On success, we redirect the user to the /dashboard route, which can contain information like username, ID, login time, etc.</p><p>Our dashboard.vue file looks like this:</p><p><strong>📂 pages/dashboard.vue</strong></p><pre>&lt;template&gt;<br>  &lt;header&gt;<br>    &lt;template v-if=&quot;loggedIn&quot;&gt;<br>      &lt;p&gt;<br>        Hi<br>        {{ user?.username }}<br>      &lt;/p&gt;<br>      &lt;p @click=&quot;logout&quot;&gt;Log out&lt;/p&gt;<br>    &lt;/template&gt;<br>    &lt;p v-else&gt;Hi Guest, &lt;nuxt-link :to=&quot;{ name: &#39;login&#39; }&quot;&gt;Login&lt;/nuxt-link&gt;&lt;/p&gt;<br>  &lt;/header&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>const { loggedIn, user, clear, fetch } = useUserSession();<br>fetch()<br>const logout = () =&gt; {<br>  clear();<br>};<br>&lt;/script&gt;<br>&lt;style lang=&quot;scss&quot; scoped&gt;&lt;/style&gt;</pre><p>Here, we use the Nuxt Auth Utils composable to fetch log-in status (loggedIn), user information (user), the fetch function that fetches the updated user information, and logout function (clear). With this data available, we can display the username of the currently authenticated user.</p><p>Our login.js API looks like this:</p><p><strong>📂 server/api/auth/login.js</strong></p><pre>import bcrypt from &quot;bcrypt&quot;;<br>import { initDb } from &quot;../../db/database&quot;;<br><br>export default defineEventHandler(async (event) =&gt; {<br>  try {<br>    const body = await readBody(event); // Retrieve request body<br>    if (!body) {<br>      console.error(&quot;Request body is empty or undefined&quot;);<br>      return createError({<br>        statusCode: 400,<br>        statusMessage: &quot;Request body is empty or undefined&quot;,<br>      });<br>    }<br>    const { username, password } = body;<br>    if (!username || !password) {<br>      console.error(&quot;Username or password missing&quot;);<br>      return createError({<br>        statusCode: 400,<br>        statusMessage: &quot;Username and password are required&quot;,<br>      });<br>    }<br>    const db = await initDb(); // Initialize database connection<br>    const user = await db.get(&quot;SELECT * FROM users WHERE username = ?&quot;, [<br>      username,<br>    ]);<br>    // For security reasons, do not specify if username or password is incorrect<br>    if (!user || !(await bcrypt.compare(password, user.password))) {<br>      console.error(`Invalid username or password for user: ${username}`);<br>      return createError({<br>        statusCode: 401,<br>        statusMessage: &quot;Invalid username or password&quot;,<br>      });<br>    } else {<br>      const userData = { username: user.username };<br>      await setUserSession(event, {<br>        user: userData,<br>        loggedInAt: new Date(),<br>      });<br>    }<br>    return { success: true, user };<br>  } catch (error) {<br>    console.error(&quot;Error handling login request:&quot;, error);<br>    return createError({<br>      statusCode: 500,<br>      statusMessage: &quot;Failed to process request&quot;,<br>    });<br>  }<br>});</pre><p>This file is similar to the signup.js file as we use defineEventHandler to declare our login function. After verifying the body of the login request contains both username and password, we initialize a connection to the database using our imported initDb function.</p><p>Upon a successful connection, we query the database for a matching username using the .get(&quot;SELECT * FROM users WHERE username = ?&quot;,[username]) where username 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 bcrypt.compare method and return an error if the passwords do not match.</p><p>If the user exists in the database, we set the user session by calling setUserSession and also pass user and loggedInAt to our session data.</p><p>Finally, we use createError to handle all the possible errors that may occur from this request and attach the appropriate status code and explanatory messages.</p><p>To test our login function, we create a login.vue in the pages/ directory.</p><p><strong>📂 pages/login.vue</strong></p><pre>&lt;template&gt;<br>  &lt;div class=&quot;login&quot;&gt;<br>    &lt;AuthForm :loading=&quot;loading&quot; @submit=&quot;login&quot; title=&quot;Sign in&quot; /&gt;<br>    &lt;p class=&quot;login__text&quot;&gt;<br>      New here?<br>      &lt;nuxt-link :to=&quot;{ name: &#39;signup&#39; }&quot;&gt;Sign up&lt;/nuxt-link&gt;<br>    &lt;/p&gt;<br>  &lt;/div&gt;<br>&lt;/template&gt;<br><br>&lt;script setup&gt;<br>import AuthForm from &quot;@/components/AuthForm.vue&quot;;<br>const loading = ref(false);<br>const router = useRouter();<br>const login = async (body) =&gt; {<br>  loading.value = true;<br>  try {<br>    await $fetch(&quot;/api/auth/login&quot;, {<br>      method: &quot;POST&quot;,<br>      body,<br>    });<br>    router.push({name: &#39;Dashboard&#39;})<br>    loading.value = false;<br>  } catch (error) {<br>    console.log({error});<br>    alert(error.statusMessage ||error);<br>    loading.value = false;<br>  }<br>};<br>&lt;/script&gt;<br>&lt;style lang=&quot;scss&quot; scoped&gt;<br>.login {<br>  width: 100%;<br>  padding: 50px;<br>  max-width: 400px;<br>  margin: auto;<br>  color: #333333;<br>  &amp;__text {<br>    text-align: right;<br>    &amp; a {<br>      text-decoration: underline;<br>      color: inherit;<br>    }<br>  }<br>}<br>&lt;/style&gt;</pre><p>In this file, we use the AuthForm component to collect the user details and pass them to the login function. In this function, we call our login endpoint /api/auth/login and pass the payload which we get from the AuthForm component.</p><p>On a successful login, we redirect the user to the /dashboard page by calling router.push({name: &#39;Dashboard&#39;}) where Dashboard is the route name which we show the username and a log out button.</p><p>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.</p><h3>Wrapping up</h3><p>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.</p><p>With Nuxt Auth Utils, managing user sessions becomes seamless, secure, and efficient, ensuring your application provides a reliable and secure authentication experience and <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">CSRF protection</a>. To further your learning, keep an eye out for our upcoming Nuxt Authentication course here on Vue Mastery!</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/minimalist-nuxt-authentication"><em>https://www.vuemastery.com</em></a><em> on July 12, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8de8caac4117" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/minimalist-nuxt-authentication-8de8caac4117">Minimalist Nuxt Authentication</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Upgrading to Nuxt 4]]></title>
            <link>https://medium.com/vue-mastery/upgrading-to-nuxt-4-98966f148f25?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/98966f148f25</guid>
            <category><![CDATA[nuxt]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[vue]]></category>
            <category><![CDATA[front-end-development]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Mon, 24 Jun 2024 21:49:41 GMT</pubDate>
            <atom:updated>2024-06-24T21:56:11.261Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qrd_wR2gjCfwI0RX-RQHSg.jpeg" /></figure><p>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.</p><p>At the time of this writing, nuxt v4 has not been released yet. But you can still try it out using v3.12.</p><p>First, upgrade the Nuxt version in your app:</p><pre>npx nuxi@latest upgrade</pre><p>This will upgrade your project to the latest Nuxt version.</p><p>With v3.12, you have to set the compatibilityVersion option to 4: <strong>nuxt.config.ts</strong></p><pre>export default defineNuxtConfig({<br>  future: {<br>    compatibilityVersion: 4<br>  },<br>  ...<br>})</pre><p>Next, let’s talk about the changes you should be expecting in Nuxt 4.</p><h3>Nuxt 4 Folder Structure</h3><p>The most obvious change in Nuxt v4 is the new folder structure:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5j5HidvShWGXayCu" /></figure><p>Now you have to put the client-side code in the <strong>app</strong> folder instead of the root folder. But the server folder can stay where it is.</p><p>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.</p><p>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.</p><h3>useAsyncData &amp; useFetch</h3><p>There are a handful of miscellaneous changes with useAsyncData and useFetch that you need to be mindful of.</p><p>First of all, the data fetched using useAsyncData and useFetch will be cached and made available to other pre-rendered pages without refetching.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*39_uHJd6ien0nxpM" /></figure><p>This data-sharing feature has been experimental, but in Nuxt 4, it’s a real feature.</p><p>The data ref that gets returned will now be a shallow ref:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Q8BpfHoC4dv0W2WF" /></figure><p>(Shallow ref will only be reactive if the .value itself is reassigned.)</p><p>Both of these composables also return a refresh function that you can call to refetch the data. And this refresh function can be configured with a dedupe option:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_iL6Y7o2IxwvyNGB" /></figure><p>Instead of using true and false, now you have to use cancel or defer to set the dedupe 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.</p><p>When useAsyncData is configured with a default value, refreshNuxtData will reset the data back to that default value:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YF4_AmeArsJtJSgQ" /></figure><p>(Previously, this would reset the comments to undefined. And since useFetch couldn’t be configured with a default value, this doesn’t affect useFetch.)</p><p>On a related note, if you didn’t set a default value, it will now default to undefined. This applies to both useAsyncData and useFetch:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1AIzIbmtWADDnme0" /></figure><p>(Previously, it defaults to null.)</p><h3>Other Nuxt 4 Changes</h3><p>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:</p><ul><li>Now Nuxt scans the <strong>index.js</strong> files in the child folders inside <strong>/middleware</strong>.</li><li>Now the builder:watch hook emits an absolute path, instead of a relative path.</li><li>Some template-specific code generation utils have been removed, and <strong>.ejs</strong> file compilation is removed, too.</li><li>Some experimental features have also been removed, so their corresponding config options have been removed too: treeshakeClientOnly, configSchema, polyfillVueUseHead, respectNoSSRHeader.</li></ul><h3>Where to go next?</h3><p>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.</p><p>If you enjoy visually illustrated content like this tutorial, you should check out the <a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro">Real World Nuxt</a> course and the <a href="https://www.vuemastery.com/courses/nuxt-api-routes/api-routes-introduction">Nuxt API Routes</a> course here on <a href="https://www.vuemastery.com/">VueMastery.com</a>.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/upgrading-to-nuxt-4"><em>https://www.vuemastery.com</em></a><em> on June 24, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98966f148f25" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/upgrading-to-nuxt-4-98966f148f25">Upgrading to Nuxt 4</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nuxt’s Server-Only Components should be on your radar]]></title>
            <link>https://medium.com/vue-mastery/nuxts-server-only-components-should-be-on-your-radar-827ad4b0bc50?source=rss----99ddd8223b51---4</link>
            <guid isPermaLink="false">https://medium.com/p/827ad4b0bc50</guid>
            <category><![CDATA[nuxtjs]]></category>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[vuejs]]></category>
            <dc:creator><![CDATA[Vue Mastery]]></dc:creator>
            <pubDate>Mon, 25 Mar 2024 23:08:21 GMT</pubDate>
            <atom:updated>2024-03-25T23:08:21.053Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wBNz9QuED0RkT18d7anoBA.jpeg" /></figure><p><em>Written by Andy Li</em></p><p>Have you heard of <em>React Server Component</em>? It’s a newer type of component rendering that has been making a wave in the React community in the past year.</p><p>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.</p><p>React Server Component is all about reducing the need for hydration.</p><p>Nuxt.js now has something similar called <strong>Server-Only Component</strong> (or just <strong>Server Component</strong>). As of Nuxt v3.11, it’s still an experimental feature. But we can try it out and see what it’s all about.</p><h3>Server-Only Rendering</h3><p>As it sounds, “<strong>Server-only Component</strong>” 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.</p><p>To use this feature, there’s no need to change your code. Just add the extension <strong>.server</strong> before the <strong>.vue</strong> extension.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*4DxJAEQFBq2euuML" /></figure><p>(you can clone this sample app <a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro">here</a>, it’s from my course <a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro">Real World Nuxt 3</a>)</p><p>You can add the extension to any component or page component that you want to render only on the server.</p><h3>Pros and Cons</h3><p>The good thing is that the page will now load faster without an additional hydration step on the front end.</p><p>Here’s a performance comparison on loading the page.</p><p><strong>Before:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*nbt15Rcgg6PQvir-" /></figure><p><strong>After:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*gJJw7cPnGA3ILmTF" /></figure><p>(ignore <strong>Idle</strong> and <strong>Total</strong>, they are not relevant to the page’s performance)</p><p>Look at the <strong>Scripting</strong> part (the yellow part), that’s where the hydration takes place. Since there’s no hydration with server-only rendering, there’s less <strong>Scripting</strong> work needed to be done while loading the page.</p><p>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.</p><p>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.</p><p>In practice, we would have to mix and match different types of components to get the desired results.</p><h3>Mixing Server-Only Components and SSR</h3><p>Once <strong>server-only components</strong> 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 <em>top-down</em> approach.</p><p>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 <strong>.server</strong> extension to the components you want to render as server-only. This is the <em>bottom-up</em> approach.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*SVr7POOidwZWgFxS" /></figure><p>The top-down approach is great for pages with mostly static content, but where there’s a bit of interactive UI.</p><p>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.</p><p>As of version 3.11.1, this feature is still under development, so the final developer experience (DX) remains uncertain.</p><p>However, the server-only component feature is built on the &lt;NuxtIsland&gt; component. The DX of using &lt;NuxtIsland&gt; is more defined, so we’ll discuss that next.</p><h3>Nuxt Island</h3><p>Instead of making an entire component server-only, we can render a specific component in the template server-only by using the &lt;NuxtIsland&gt; component.</p><p>For example, with this code (from the <a href="https://github.com/Code-Pop/Real-World-Nuxt-3/tree/L6-end">sample project</a>):</p><p><strong>/pages/posts/[post].vue</strong></p><pre>&lt;template&gt;<br>  &lt;main&gt;<br>    &lt;template v-if=&quot;post&quot;&gt;<br>      &lt;h1&gt;<br>        {{ post.title }}<br>        &lt;CategoryLink :category=&quot;post.category&quot; /&gt;<br>      &lt;/h1&gt;<br>      &lt;RenderMarkdown :source=&quot;post.content&quot; /&gt;<br>    &lt;/template&gt;<br>  &lt;/main&gt;<br>&lt;/template&gt;</pre><p>The code is rendering some markdown content using a component called RenderMarkdown.</p><p>We can render it in the server-only mode through &lt;NuxtIsland&gt;:</p><p><strong>/pages/posts/[post].vue</strong></p><pre>&lt;template&gt;<br>  &lt;main&gt;<br>    &lt;template v-if=&quot;post&quot;&gt;<br>      &lt;h1&gt;<br>        {{ post.title }}<br>        &lt;CategoryLink :category=&quot;post.category&quot; /&gt;<br>      &lt;/h1&gt;<br>      &lt;NuxtIsland<br>        name=&quot;RenderMarkdown&quot;<br>        :props=&quot;{ source: post.content }&quot;<br>      /&gt;<br>    &lt;/template&gt;<br>  &lt;/main&gt;<br>&lt;/template&gt;</pre><p>RenderMarkdown has to have the <strong>.island.vue</strong> extension for this to work.</p><p>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.</p><p>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</p><p>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.</p><p>So with &lt;NuxtIsland&gt;, you have the freedom to choose how a component should be rendered when you’re using it, not when you’re creating it.</p><h3>Looking Ahead</h3><p>The Nuxt team has mentioned that v3.11 is likely the last minor update before the next major version. Hopefully, we’ll see <strong>server-only component</strong> as a stable feature soon in Nuxt v4.</p><p>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 <a href="https://www.vuemastery.com/courses/real-world-nuxt-3/intro">Real World Nuxt 3</a> course on Vue Mastery to learn all about that.</p><p><em>Originally published at </em><a href="https://www.vuemastery.com/blog/nuxts-server-only-components-should-be-on-your-radar"><em>https://www.vuemastery.com</em></a><em> on March 25, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=827ad4b0bc50" width="1" height="1" alt=""><hr><p><a href="https://medium.com/vue-mastery/nuxts-server-only-components-should-be-on-your-radar-827ad4b0bc50">Nuxt’s Server-Only Components should be on your radar</a> was originally published in <a href="https://medium.com/vue-mastery">Vue Mastery</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>