<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nuxt on Techformist</title>
    <link>https://techformist.com/tags/nuxt/</link>
    <description>Recent content in Nuxt on Techformist</description>
    <image>
      <url>https://techformist.com/logo.svg</url>
      <link>https://techformist.com/logo.svg</link>
    </image>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 25 Mar 2025 06:30:00 +0000</lastBuildDate><atom:link href="https://techformist.com/tags/nuxt/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Introducing utils@techformist - A Developer&#39;s Swiss Army Knife</title>
      <link>https://techformist.com/free-developer-tools/</link>
      <pubDate>Tue, 25 Mar 2025 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/free-developer-tools/</guid>
      <description>&lt;p&gt;Today, I&amp;rsquo;m excited to introduce &lt;strong&gt;utils.techformist.com&lt;/strong&gt; – a collection of client-side web utilities designed to make developers&amp;rsquo; lives easier. Built with speed, simplicity, and privacy in mind, this tool suite aims to be your go-to resource for common development tasks.&lt;/p&gt;
&lt;h2 id=&#34;why-another-utility-site&#34;&gt;Why Another Utility Site?&lt;/h2&gt;
&lt;p&gt;The web is full of utility sites, but many come with drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intrusive ads and analytics&lt;/li&gt;
&lt;li&gt;Server-side processing of potentially sensitive data&lt;/li&gt;
&lt;li&gt;Slow loading times&lt;/li&gt;
&lt;li&gt;Inconsistent UI/UX&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://utils.techformist.com&#34;&gt;utils.techformist.com&lt;/a&gt; addresses these issues by keeping everything client-side, ensuring your data never leaves your browser. The site loads quickly, works offline, and offers a clean, consistent interface for all tools.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today, I&rsquo;m excited to introduce <strong>utils.techformist.com</strong> – a collection of client-side web utilities designed to make developers&rsquo; lives easier. Built with speed, simplicity, and privacy in mind, this tool suite aims to be your go-to resource for common development tasks.</p>
<h2 id="why-another-utility-site">Why Another Utility Site?</h2>
<p>The web is full of utility sites, but many come with drawbacks:</p>
<ul>
<li>Intrusive ads and analytics</li>
<li>Server-side processing of potentially sensitive data</li>
<li>Slow loading times</li>
<li>Inconsistent UI/UX</li>
</ul>
<p><a href="https://utils.techformist.com">utils.techformist.com</a> addresses these issues by keeping everything client-side, ensuring your data never leaves your browser. The site loads quickly, works offline, and offers a clean, consistent interface for all tools.</p>
<p><img loading="lazy" src="/2025/great-developer-tools-free.png" type="" alt="great-developer-tools-free"  /></p>
<h2 id="key-features">Key Features</h2>
<h3 id="1-tools-that-you-want-to-use">1. Tools that you want to use!</h3>
<p>Our collection includes essential utilities that solve real problems :)</p>
<ul>
<li><strong>Calculator</strong>: A simple yet powerful calculator for quick arithmetic operations.</li>
<li><strong>QR Code Generator</strong>: Create QR codes for URLs, WiFi networks, contact details, and more with customizable colors.</li>
<li><strong>Password Generator</strong>: Generate secure, random passwords with options for length, character types, and complexity.</li>
<li><strong>Barcode Generator</strong>: Create various barcode formats with customizable settings for professional or personal use.</li>
<li><strong>Image Converter</strong>: Convert images between different formats without uploading them to any server.</li>
<li><strong>Favicon Generator</strong>: Quickly create favicons for your website from any image.</li>
<li><strong>Color Picker</strong>: Extract and analyze colors from images with support for multiple color formats.</li>
<li><strong>JSON Formatter</strong>: Format, validate, and beautify JSON data with easy-to-use controls and copy functionality.</li>
<li><strong>Base64 Converter</strong>: Encode and decode text or files to and from Base64 format right in your browser.</li>
</ul>
<p>.. and so on. Check out the site for the latest updates.</p>
<h3 id="2-fast-search-and-category-based-organization">2. Fast Search and Category-Based Organization</h3>
<p>A simple tool search is at the heart of the site!
At the same time, tools are organized into logical categories for easy discovery:</p>
<ul>
<li><strong>Code</strong>: Developer tools like JSON Formatter and Base64 Converter for everyday coding tasks</li>
<li><strong>Image</strong>: Tools for working with images, including QR Code Generator, Barcode Generator, Image Converter, and Favicon Generator</li>
<li><strong>Text</strong>: Text manipulation utilities like the Password Generator</li>
<li><strong>Design</strong>: Creative tools such as the Color Picker for design work</li>
<li><strong>File</strong>: File processing tools like the PDF Compressor</li>
<li><strong>Other</strong>: Miscellaneous utilities including the Calculator</li>
</ul>
<p>This categorization ensures you can quickly find the right tool for your current task.</p>
<h3 id="3-responsive-and-accessible-design">3. Responsive and Accessible Design</h3>
<p>All tools feature:</p>
<ul>
<li>Fully responsive layouts that work on devices of any size</li>
<li>Dark/light mode support</li>
</ul>
<h3 id="4-privacy-first-approach">4. Privacy-First Approach</h3>
<p>What sets util.techformist.com apart is its commitment to privacy:</p>
<ul>
<li>No tracking (except essentials)</li>
<li>No server-side processing</li>
<li>No data storage outside of the browser</li>
<li>No data transmission to any external computer outside of your own browser</li>
</ul>
<h2 id="technology-stack">Technology Stack</h2>
<p>Utils.techformist.com is built on modern web technologies:</p>
<ul>
<li><strong>Nuxt 3</strong>: For its excellent developer experience, automatic routing, and performance optimizations</li>
<li><strong>NuxtUI</strong>: Providing a consistent design system and accessible components</li>
<li><strong>Tailwind CSS</strong>: For rapid styling and responsive design</li>
</ul>
<p>The app is client-side only - no database or server processing.</p>
<h2 id="why-client-side-only">Why Client-Side Only?</h2>
<p>The decision to make util.techformist.com entirely client-side was deliberate:</p>
<ol>
<li><strong>Privacy</strong>: Your data never leaves your browser, eliminating privacy concerns.</li>
<li><strong>Performance</strong>: No server roundtrips means instant processing of your tasks.</li>
<li><strong>Availability</strong>: The site works offline once loaded.</li>
<li><strong>Scalability</strong>: Without server costs, the site can scale infinitely.</li>
<li><strong>Simplicity</strong>: The architecture remains clean without databases or APIs.</li>
</ol>
<h2 id="development-approach">Development Approach</h2>
<p>Building the site was straightforward with Nuxt&rsquo;s module system and NuxtUI&rsquo;s component library. The development workflow included:</p>
<ol>
<li>Setting up a Nuxt 3 project with the content and UI modules</li>
<li>Creating a consistent layout and navigation system</li>
<li>Implementing individual utility components</li>
<li>Ensuring responsive design with Tailwind</li>
<li>Optimizing for performance</li>
</ol>
<p>The entire project required no backend infrastructure, making deployment simple through static site hosting.
The site is hosted on Cloudflare Pages.</p>
<h2 id="looking-ahead">Looking Ahead</h2>
<p>Util.techformist.com is just getting started. Future plans include:</p>
<ul>
<li>Adding more utility tools based on user feedback!</li>
<li>Implementing more advanced features like tool chaining :)</li>
<li>Improving keyboard shortcuts and accessibility</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Util.techformist.com represents my philosophy that web tools should be fast, private, and straightforward. By keeping everything in the browser, I&rsquo;ve created a resource that respects your data while providing genuinely useful functionality.</p>
<p>Give it a try at <a href="https://utils.techformist.com">utils.techformist.com</a> and let me know what you think.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Create a Client-Side PDF Generator with Nuxt 3</title>
      <link>https://techformist.com/create-pdf-json-nuxt/</link>
      <pubDate>Sat, 01 Mar 2025 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/create-pdf-json-nuxt/</guid>
      <description>&lt;p&gt;We will walk through the steps to build a document analysis web application using Nuxt 3 and Azure Document Intelligence. This application allows users to -&lt;/p&gt;
&lt;p&gt;Features will include -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🔍 Create/pass a template in HTML and placeholders for values&lt;/li&gt;
&lt;li&gt;🕶️ Real-time document analysis status&lt;/li&gt;
&lt;li&gt;💾 Download merged content as PDF&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s a quick demo.
&lt;img loading=&#34;lazy&#34; src=&#34;https://techformist.com/2025/docgen-pdf-generator-json-nuxt.gif&#34; type=&#34;&#34; alt=&#34;docgen-pdf-generator-json-nuxt&#34;  /&gt;&lt;/p&gt;
&lt;p&gt;While the demo showcases invoice generation, the same program can be extended to any document type - filled application forms, documents, etc.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>We will walk through the steps to build a document analysis web application using Nuxt 3 and Azure Document Intelligence. This application allows users to -</p>
<p>Features will include -</p>
<ul>
<li>🔍 Create/pass a template in HTML and placeholders for values</li>
<li>🕶️ Real-time document analysis status</li>
<li>💾 Download merged content as PDF</li>
</ul>
<p>Here&rsquo;s a quick demo.
<img loading="lazy" src="/2025/docgen-pdf-generator-json-nuxt.gif" type="" alt="docgen-pdf-generator-json-nuxt"  /></p>
<p>While the demo showcases invoice generation, the same program can be extended to any document type - filled application forms, documents, etc.</p>
<p>Technology stack -</p>
<ol>
<li>NuxtJS</li>
<li>Tailwind CSS</li>
<li>PDFJS</li>
<li>CKEditor</li>
</ol>
<h2 id="why-client-side-pdf-generation">Why Client-Side PDF Generation?</h2>
<p>Generating PDFs on the client-side offers several benefits:</p>
<ul>
<li><strong>Performance:</strong> Offloading PDF generation to the client&rsquo;s device reduces server load and leads to faster response times.</li>
<li><strong>User Experience:</strong> Users can preview changes in real-time, adjust their inputs, and generate PDFs without a trip to the server.</li>
<li><strong>Cost-Effective:</strong> Avoid additional server infrastructure by leveraging modern browsers and their capabilities.</li>
<li><strong>Flexibility:</strong> Easily integrate rich text editing and custom templates to create personalized documents.</li>
</ul>
<p>Who is this post for?</p>
<ul>
<li>You are trying to roll-out a PDF generator for invoices or filled applications</li>
<li>You are using an external service to generate PDFs</li>
<li>You are not satisfied with server-side PDF generators</li>
</ul>
<h2 id="setting-up-the-project">Setting Up the Project</h2>
<p>Before we start, make sure you have Node.js 18.x or later installed in your computer.</p>
<p>Create a blank Nuxt app.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bunx nuxi@latest init docgen
</span></span></code></pre></div><p>A key component of our PDF generator is a rich text editor to create templates. We will use CKEditor to achieve just that. We will also use a JSON editor to enable editing JSON data.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bun i ckeditor/ckeditor5-vue ckeditor5 vue3-json-editor
</span></span></code></pre></div><p>We will use pdfjs to preview and generate PDF, and DomPurify to make sure we are rendering things safer. Also, install types while at it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bun i dompurify html2canvas jspdf pdfjs-dist pdfmake
</span></span><span class="line"><span class="cl">bun i -d @types/pdfmake
</span></span></code></pre></div><p>Add styling and icons.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bunx nuxi@latest module add tailwindcss lucide-vue-next
</span></span></code></pre></div><h2 id="develop-the-app">Develop the app</h2>
<h3 id="rich-text-editor">Rich Text Editor</h3>
<p>Rich text editor is one of the main components that enables users to edit templates.
You will not need the rich text editor if you don&rsquo;t plan to edit templates within the app (the template is plain HTML anyway.)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;richtext-editor&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ckeditor</span>
</span></span><span class="line"><span class="cl">      <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;editor &amp;&amp; config &amp;&amp; modelValue&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">      <span class="nt">:editor</span><span class="o">=</span><span class="s">&#34;editor&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;modelValue&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">      <span class="nt">:config</span><span class="o">=</span><span class="s">&#34;config&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">      <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;previewContent&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">      <span class="na">class</span><span class="o">=</span><span class="s">&#34;preview-overlay prose&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">v-html</span><span class="o">=</span><span class="s">&#34;previewContent&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">    <span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">onMounted</span><span class="p">,</span> <span class="nx">ref</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ClassicEditor</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Autoformat</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">AutoImage</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Autosave</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Base64UploadAdapter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Bold</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Essentials</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// other items - see GitHub link
</span></span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;ckeditor5&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;ckeditor5/ckeditor5.css&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Ckeditor</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;@ckeditor/ckeditor5-vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;ckeditor5/ckeditor5.css&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">isLayoutReady</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">editor</span> <span class="o">=</span> <span class="nx">ClassicEditor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">LICENSE_KEY</span> <span class="o">=</span> <span class="s2">&#34;GPL&#34;</span><span class="p">;</span> <span class="c1">// or &lt;YOUR_LICENSE_KEY&gt;.
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">isLayoutReady</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">toolbar</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">items</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sourceEditing&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;|&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;heading&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;|&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;fontSize&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;fontFamily&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// other toolbar items. See GitHub link for all buttons
</span></span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">shouldNotGroupWhenFull</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// relevant plugins - see GitHub link
</span></span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fontFamily</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">supportAllValues</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fontSize</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">options</span><span class="o">:</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="s2">&#34;default&#34;</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">22</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">supportAllValues</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">heading</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">options</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;paragraph&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Paragraph&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_paragraph&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">model</span><span class="o">:</span> <span class="s2">&#34;heading6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">view</span><span class="o">:</span> <span class="s2">&#34;h6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s2">&#34;Heading 6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="kr">class</span><span class="o">:</span> <span class="s2">&#34;ck-heading_heading6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">image</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">toolbar</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;imageTextAlternative&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">initialData</span><span class="o">:</span> <span class="s2">&#34;..&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">licenseKey</span><span class="o">:</span> <span class="nx">LICENSE_KEY</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">link</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">addTargetToExternalLinks</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">defaultProtocol</span><span class="o">:</span> <span class="s2">&#34;https://&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">decorators</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">toggleDownloadable</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">mode</span><span class="o">:</span> <span class="s2">&#34;manual&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">label</span><span class="o">:</span> <span class="s2">&#34;Downloadable&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">attributes</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">download</span><span class="o">:</span> <span class="s2">&#34;file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">placeholder</span><span class="o">:</span> <span class="s2">&#34;Type or paste your content here!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">table</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">contentToolbar</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tableColumn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tableRow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;mergeTableCells&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tableProperties&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;tableCellProperties&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">modelValue</span> <span class="o">=</span> <span class="nx">defineModel</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">props</span> <span class="o">=</span> <span class="nx">defineProps</span><span class="o">&lt;</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">previewContent</span><span class="o">?:</span> <span class="nx">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">onMounted</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isLayoutReady</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* include styles */</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="json-editor">JSON Editor</h3>
<p>Create the JSON editor that is used to edit JSON data.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c">&lt;!--</span> <span class="nx">components</span><span class="o">/</span><span class="nx">DataEditor</span><span class="p">.</span><span class="nx">vue</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ClientOnly</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">      <span class="na">class</span><span class="o">=</span><span class="s">&#34;h-[300px] border border-white/20 rounded-lg overflow-hidden bg-white/5&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">textarea</span>
</span></span><span class="line"><span class="cl">        <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;jsonString&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">        <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-full h-full p-4 font-mono text-sm bg-transparent text-black dark:text-white/90 focus:outline-none&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">@input</span><span class="s">=&#34;handleInput&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">        <span class="na">spellcheck</span><span class="o">=</span><span class="s">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&gt;&lt;/</span><span class="nt">textarea</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;error&#34; class=&#34;mt-2 text-red-400 text-sm&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{{</span> <span class="na">error</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">ClientOnly</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ref</span><span class="p">,</span> <span class="nx">watch</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useDocumentStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/stores/document&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">props</span> <span class="o">=</span> <span class="nx">defineProps</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">modelValue</span><span class="o">:</span> <span class="nx">Record</span><span class="p">&lt;</span><span class="nt">string</span><span class="err">,</span> <span class="na">any</span><span class="p">&gt;</span> <span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">emit</span> <span class="o">=</span> <span class="nx">defineEmits</span><span class="o">&lt;</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;update:modelValue&#34;</span><span class="o">:</span> <span class="p">[</span><span class="nx">value</span><span class="o">:</span> <span class="nx">Record</span><span class="p">&lt;</span><span class="nt">string</span><span class="err">,</span> <span class="na">any</span><span class="p">&gt;];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">documentStore</span> <span class="o">=</span> <span class="nx">useDocumentStore</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">jsonString</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">modelValue</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">props</span><span class="p">.</span><span class="nx">modelValue</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nx">newVal</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">newString</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">newVal</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">jsonString</span><span class="p">.</span><span class="nx">value</span> <span class="o">!==</span> <span class="nx">newString</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">jsonString</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">newString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">handleInput</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="o">:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span> <span class="nx">as</span> <span class="nx">HTMLTextAreaElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">parsed</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">documentStore</span><span class="p">.</span><span class="nx">isDirty</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">emit</span><span class="p">(</span><span class="s2">&#34;update:modelValue&#34;</span><span class="p">,</span> <span class="nx">parsed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;Invalid JSON format&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* include styles */</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="create-pdf-viewer">Create PDF Viewer</h3>
<p>PDF viewer shows the PDF after merging data in template.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c">&lt;!--</span> <span class="nx">components</span><span class="o">/</span><span class="nx">PdfViewer</span><span class="p">.</span><span class="nx">vue</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pdf-viewer&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;loading&#34; class=&#34;loading-indicator&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;spinner&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Generating</span> <span class="nx">PDF</span> <span class="nx">preview</span><span class="p">...&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-else-if</span><span class="o">=</span><span class="s">&#34;error&#34; class=&#34;error-message&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{{</span> <span class="na">error</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-else</span><span class="p"> </span><span class="na">ref</span><span class="o">=</span><span class="s">&#34;canvasContainer&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;canvas-container&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ref</span><span class="p">,</span> <span class="nx">onMounted</span><span class="p">,</span> <span class="nx">watch</span><span class="p">,</span> <span class="nx">onBeforeUnmount</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">props</span> <span class="o">=</span> <span class="nx">defineProps</span><span class="o">&lt;</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">content</span><span class="o">:</span> <span class="nx">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">canvasContainer</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">HTMLElement</span> <span class="err">|</span> <span class="na">null</span><span class="p">&gt;(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">loading</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">string</span> <span class="err">|</span> <span class="na">null</span><span class="p">&gt;(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">pdfDoc</span><span class="o">:</span> <span class="nx">any</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">currentPage</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">pageRendering</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">pageNumPending</span><span class="o">:</span> <span class="nx">number</span> <span class="o">|</span> <span class="kc">null</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">pdfjsLib</span><span class="o">:</span> <span class="nx">any</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Clean up any resources when component is unmounted
</span></span></span><span class="line"><span class="cl"><span class="nx">onBeforeUnmount</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">pdfDoc</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pdfDoc</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Initialize PDF.js
</span></span></span><span class="line"><span class="cl"><span class="nx">onMounted</span><span class="p">(</span><span class="nx">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">client</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// Import PDF.js dynamically
</span></span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">pdfjsModule</span> <span class="o">=</span> <span class="nx">await</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&#34;pdfjs-dist&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">pdfjsLib</span> <span class="o">=</span> <span class="nx">pdfjsModule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Set worker source - using CDN for the worker
</span></span></span><span class="line"><span class="cl">      <span class="nx">pdfjsLib</span><span class="p">.</span><span class="nx">GlobalWorkerOptions</span><span class="p">.</span><span class="nx">workerSrc</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Generate and render PDF
</span></span></span><span class="line"><span class="cl">      <span class="nx">await</span> <span class="nx">generatePdf</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error initializing PDF.js:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;Failed to initialize PDF viewer. Please try again.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Watch for content changes
</span></span></span><span class="line"><span class="cl"><span class="nx">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">props</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">pdfjsLib</span> <span class="o">&amp;&amp;</span> <span class="nx">process</span><span class="p">.</span><span class="nx">client</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">await</span> <span class="nx">generatePdf</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Generate PDF from content
</span></span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">generatePdf</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">pdfjsLib</span> <span class="o">||</span> <span class="o">!</span><span class="nx">process</span><span class="p">.</span><span class="nx">client</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Import pdfMake dynamically
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pdfMakeModule</span> <span class="o">=</span> <span class="nx">await</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&#34;pdfmake/build/pdfmake&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pdfFontsModule</span> <span class="o">=</span> <span class="nx">await</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&#34;pdfmake/build/vfs_fonts&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the default export or the module itself
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pdfMake</span> <span class="o">=</span> <span class="nx">pdfMakeModule</span><span class="p">.</span><span class="k">default</span> <span class="o">||</span> <span class="nx">pdfMakeModule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Manually set up the vfs with a basic font
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">pdfMake</span><span class="p">.</span><span class="nx">vfs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// @ts-ignore - We know this property might exist
</span></span></span><span class="line"><span class="cl">      <span class="nx">pdfMake</span><span class="p">.</span><span class="nx">vfs</span> <span class="o">=</span> <span class="nx">pdfFontsModule</span><span class="p">.</span><span class="nx">pdfMake</span><span class="o">?</span><span class="p">.</span><span class="nx">vfs</span> <span class="o">||</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Create a temporary element to parse the HTML
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">tempDiv</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;div&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tempDiv</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">props</span><span class="p">.</span><span class="nx">content</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Extract text content and structure
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pdfContent</span> <span class="o">=</span> <span class="nx">extractPdfContent</span><span class="p">(</span><span class="nx">tempDiv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Define the document with basic fonts
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">docDefinition</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">content</span><span class="o">:</span> <span class="nx">pdfContent</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">defaultStyle</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">font</span><span class="o">:</span> <span class="s2">&#34;Roboto&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fontSize</span><span class="o">:</span> <span class="mi">12</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">lineHeight</span><span class="o">:</span> <span class="mf">1.5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">pageSize</span><span class="o">:</span> <span class="s2">&#34;A4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">pageMargins</span><span class="o">:</span> <span class="p">[</span><span class="mi">40</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">60</span><span class="p">]</span> <span class="nx">as</span> <span class="p">[</span><span class="nx">number</span><span class="p">,</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">number</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Generate PDF as Blob
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pdfDocGenerator</span> <span class="o">=</span> <span class="nx">pdfMake</span><span class="p">.</span><span class="nx">createPdf</span><span class="p">(</span><span class="nx">docDefinition</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">pdfDocGenerator</span><span class="p">.</span><span class="nx">getBlob</span><span class="p">(</span><span class="nx">async</span> <span class="p">(</span><span class="nx">blob</span><span class="o">:</span> <span class="nx">Blob</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Convert blob to array buffer
</span></span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">arrayBuffer</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">blob</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Load PDF document
</span></span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">loadingTask</span> <span class="o">=</span> <span class="nx">pdfjsLib</span><span class="p">.</span><span class="nx">getDocument</span><span class="p">({</span> <span class="nx">data</span><span class="o">:</span> <span class="nx">arrayBuffer</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pdfDoc</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">loadingTask</span><span class="p">.</span><span class="nx">promise</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Initial/first page rendering
</span></span></span><span class="line"><span class="cl">        <span class="nx">await</span> <span class="nx">renderPage</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error rendering PDF:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;Failed to render PDF preview.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error generating PDF:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;Failed to generate PDF preview.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Render the specified page
</span></span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">renderPage</span><span class="p">(</span><span class="nx">num</span><span class="o">:</span> <span class="nx">number</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">pdfDoc</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">pageRendering</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Get page
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">pdfDoc</span><span class="p">.</span><span class="nx">getPage</span><span class="p">(</span><span class="nx">num</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Clear previous content
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">canvasContainer</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">canvasContainer</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Calculate scale to fit the container width
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">containerWidth</span> <span class="o">=</span> <span class="nx">canvasContainer</span><span class="p">.</span><span class="nx">value</span><span class="o">?</span><span class="p">.</span><span class="nx">clientWidth</span> <span class="o">||</span> <span class="mi">800</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">viewport</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getViewport</span><span class="p">({</span> <span class="nx">scale</span><span class="o">:</span> <span class="mi">1</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">scale</span> <span class="o">=</span> <span class="nx">containerWidth</span> <span class="o">/</span> <span class="nx">viewport</span><span class="p">.</span><span class="nx">width</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">scaledViewport</span> <span class="o">=</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getViewport</span><span class="p">({</span> <span class="nx">scale</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Create canvas for each page
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;canvas&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">&#34;2d&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">scaledViewport</span><span class="p">.</span><span class="nx">height</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">scaledViewport</span><span class="p">.</span><span class="nx">width</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">canvas</span><span class="p">.</span><span class="nx">className</span> <span class="o">=</span> <span class="s2">&#34;pdf-page&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Add canvas to container
</span></span></span><span class="line"><span class="cl">    <span class="nx">canvasContainer</span><span class="p">.</span><span class="nx">value</span><span class="o">?</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">canvas</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Render PDF page into canvas context
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">renderContext</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">canvasContext</span><span class="o">:</span> <span class="nx">ctx</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">viewport</span><span class="o">:</span> <span class="nx">scaledViewport</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="nx">renderContext</span><span class="p">).</span><span class="nx">promise</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pageRendering</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// If there&#39;s a pending page, render it
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">pageNumPending</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">renderPage</span><span class="p">(</span><span class="nx">pageNumPending</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">pageNumPending</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// If there are more pages, render them too
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">num</span> <span class="o">&lt;</span> <span class="nx">pdfDoc</span><span class="p">.</span><span class="nx">numPages</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">renderPage</span><span class="p">(</span><span class="nx">num</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error rendering page:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">pageRendering</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">error</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s2">&#34;Failed to render PDF page.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Extract content from HTML for PDF generation
</span></span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">extractPdfContent</span><span class="p">(</span><span class="nx">element</span><span class="o">:</span> <span class="nx">HTMLElement</span><span class="p">)</span><span class="o">:</span> <span class="nx">any</span><span class="p">[]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span><span class="o">:</span> <span class="nx">any</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Process child nodes
</span></span></span><span class="line"><span class="cl">  <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">childNodes</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">node</span><span class="o">:</span> <span class="nx">Node</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">===</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">TEXT_NODE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">node</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="nx">text</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeType</span> <span class="o">===</span> <span class="nx">Node</span><span class="p">.</span><span class="nx">ELEMENT_NODE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">elementNode</span> <span class="o">=</span> <span class="nx">node</span> <span class="nx">as</span> <span class="nx">HTMLElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">nodeName</span> <span class="o">=</span> <span class="nx">elementNode</span><span class="p">.</span><span class="nx">nodeName</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Handle different element types
</span></span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;p&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">text</span><span class="o">:</span> <span class="nx">elementNode</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;h1&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">text</span><span class="o">:</span> <span class="nx">elementNode</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">fontSize</span><span class="o">:</span> <span class="mi">24</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">bold</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;h2&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">text</span><span class="o">:</span> <span class="nx">elementNode</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">fontSize</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">bold</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;h3&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">text</span><span class="o">:</span> <span class="nx">elementNode</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">fontSize</span><span class="o">:</span> <span class="mi">16</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">bold</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;ul&#34;</span> <span class="o">||</span> <span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;ol&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">elementNode</span><span class="p">.</span><span class="nx">children</span><span class="p">).</span><span class="nx">map</span><span class="p">((</span><span class="nx">li</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="p">{</span> <span class="nx">text</span><span class="o">:</span> <span class="nx">li</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">ul</span><span class="o">:</span> <span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;ul&#34;</span> <span class="o">?</span> <span class="nx">items</span> <span class="o">:</span> <span class="kc">undefined</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">ol</span><span class="o">:</span> <span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;ol&#34;</span> <span class="o">?</span> <span class="nx">items</span> <span class="o">:</span> <span class="kc">undefined</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s2">&#34;table&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Extract table data
</span></span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">tableData</span><span class="o">:</span> <span class="nx">string</span><span class="p">[][]</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">        <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">elementNode</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">&#34;tr&#34;</span><span class="p">)).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">row</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="kr">const</span> <span class="nx">rowData</span><span class="o">:</span> <span class="nx">string</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">          <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">row</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">&#34;td, th&#34;</span><span class="p">)).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">cell</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">rowData</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">cell</span><span class="p">.</span><span class="nx">textContent</span><span class="o">?</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="nx">rowData</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">tableData</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">rowData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">tableData</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">            <span class="nx">table</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nx">body</span><span class="o">:</span> <span class="nx">tableData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nx">widths</span><span class="o">:</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">tableData</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="s2">&#34;*&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="nx">margin</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">10</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Recursively process other elements
</span></span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">childContent</span> <span class="o">=</span> <span class="nx">extractPdfContent</span><span class="p">(</span><span class="nx">elementNode</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">childContent</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">result</span><span class="p">.</span><span class="nx">push</span><span class="p">(...</span><span class="nx">childContent</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Include this in a <code>DocumentPreview.vue</code> component that can toggle between PDF viewer and a rich text viewer - if needed, of course.</p>
<h3 id="layout-and-page">Layout and Page</h3>
<p>Update layout -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c">&lt;!--</span> <span class="nx">layouts</span><span class="o">/</span><span class="k">default</span><span class="p">.</span><span class="nx">vue</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;min-h-screen bg-surface-ground&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">nav</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;bg-surface-card border-b border-surface-border md:px-12 px-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34; h-16 flex items-center justify-between&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">h1</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-xl font-bold text-black&#34;</span><span class="p">&gt;</span><span class="nx">docgen</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex items-center gap-4&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">            <span class="na">variant</span><span class="o">=</span><span class="s">&#34;secondary&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="o">:</span><span class="na">icon</span><span class="o">=</span><span class="s">&#34;isDark ? &#39;sun&#39; : &#39;moon&#39;&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">@click</span><span class="s">=&#34;toggleTheme&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">            <span class="o">:</span><span class="na">title</span><span class="o">=</span><span class="s">&#34;isDark ? &#39;Switch to light mode&#39; : &#39;Switch to dark mode&#39;&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">main</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34; py-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">slot</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span><span class="p">&gt;&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>&hellip; and the page.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mx-auto px-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grid grid-cols-1 lg:grid-cols-2 gap-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex flex-col gap-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toolbar&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;section-title&#34;</span><span class="p">&gt;</span><span class="nx">Template</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;h-[600px] overflow-hidden&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">ClientOnly</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">&lt;</span><span class="nt">RichTextEditor</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;template&#34; class=&#34;h-full&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">ClientOnly</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="c">&lt;!--</span> <span class="nx">Removed</span> <span class="nx">flex</span><span class="o">-</span><span class="mi">1</span> <span class="nx">from</span> <span class="nx">card</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toolbar&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;section-title&#34;</span><span class="p">&gt;</span><span class="nx">Data</span> <span class="p">(</span><span class="nx">JSON</span><span class="p">)&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;editor-container h-[400px] overflow-hidden&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">DataEditor</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;jsonData&#34; class=&#34;h-full&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="c">&lt;!--</span> <span class="nx">Removed</span> <span class="nx">flex</span><span class="o">-</span><span class="mi">1</span> <span class="nx">from</span> <span class="nx">card</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toolbar&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;section-title&#34;</span><span class="p">&gt;</span><span class="nx">Generated</span> <span class="nx">Document</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;toolbar-actions&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="c">&lt;!--</span> <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">variant</span><span class="o">=</span><span class="s">&#34;secondary&#34;</span> <span class="na">icon</span><span class="o">=</span><span class="s">&#34;refresh&#34;</span><span class="p">&gt;</span><span class="nx">Auto</span><span class="o">-</span><span class="nx">refresh</span><span class="p">&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;h-[1024px] overflow-hidden&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">DocumentPreview</span> <span class="nt">:data</span><span class="o">=</span><span class="s">&#34;jsonData&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;h-full&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useDocumentStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/stores/document&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">storeToRefs</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;pinia&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">documentStore</span> <span class="o">=</span> <span class="nx">useDocumentStore</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">template</span><span class="p">,</span> <span class="nx">jsonData</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">storeToRefs</span><span class="p">(</span><span class="nx">documentStore</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">style</span> <span class="na">scoped</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* styles */</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">style</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="run-the-app">Run the app!</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bun dev
</span></span></code></pre></div><p>This should populate some default template and data, and show you a nice preview of the merged content.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Generating PDF on client is not hard and keeps your server load low.</p>
<p>While generating PDFs client-side can certainly help in most use cases (especially for enterprise customers), it may not be a pleasant experience for mobile users. Also, features like emailing PDF, supporting non-PDF formats, etc. are hard to achieve.</p>
<p>See complete code on <a href="http://github.com/prashanth1k/docgen">GitHub</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Document Analysis App with Azure Document Intelligence</title>
      <link>https://techformist.com/document-analysis-app-nuxt/</link>
      <pubDate>Sun, 23 Feb 2025 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/document-analysis-app-nuxt/</guid>
      <description>&lt;p&gt;We will walk through the steps to build a document analysis web application using Nuxt 3 and Azure Document Intelligence. This application allows users to -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;upload PDF or images&lt;/li&gt;
&lt;li&gt;specify fields (if needed)&lt;/li&gt;
&lt;li&gt;extract specific fields of information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Features will include -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🔍 Extract custom fields from identification documents&lt;/li&gt;
&lt;li&gt;🔄 Real-time document analysis status&lt;/li&gt;
&lt;li&gt;💾 Download extracted data as JSON&lt;/li&gt;
&lt;li&gt;🎨 UI with Shadcn-vue /Tailwind CSS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s a quick demo.
&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
      &lt;iframe allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen&#34; loading=&#34;eager&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; src=&#34;https://www.youtube.com/embed/_FLmnAfiqUo?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; title=&#34;YouTube video&#34;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>We will walk through the steps to build a document analysis web application using Nuxt 3 and Azure Document Intelligence. This application allows users to -</p>
<ul>
<li>upload PDF or images</li>
<li>specify fields (if needed)</li>
<li>extract specific fields of information</li>
</ul>
<p>Features will include -</p>
<ul>
<li>🔍 Extract custom fields from identification documents</li>
<li>🔄 Real-time document analysis status</li>
<li>💾 Download extracted data as JSON</li>
<li>🎨 UI with Shadcn-vue /Tailwind CSS</li>
</ul>
<p>Here&rsquo;s a quick demo.
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/_FLmnAfiqUo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>
</p>
<p>While the demo covers only identification documents, the same program can be extended to any document type - application forms, invoices, etc.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>Before we start, make sure you have the following:</p>
<ul>
<li>Node.js 18.x or later</li>
<li>An Azure account with a Document Intelligence resource</li>
<li>Azure Document Intelligence API key and endpoint</li>
</ul>
<h2 id="step-1-setting-up-the-project">Step 1: Setting Up the Project</h2>
<p>Create a blank Nuxt app..</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">pnpx nuxi@latest init docformist
</span></span></code></pre></div><p>Get your API key and environment details from <a href="https://azure.microsoft.com/products/ai-services/ai-document-intelligence">Azure site</a>. You can sign up for free and use a Free plan to test out the service.</p>
<p>Create a <code>.env</code> file in the root directory with your Azure credentials:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">AZURE_DOCUMENT_INTELLIGENCE_KEY</span><span class="o">=</span>your_key_here
</span></span><span class="line"><span class="cl"><span class="nv">AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT</span><span class="o">=</span>your_endpoint_here
</span></span></code></pre></div><p>Add Nuxt modules -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">pnpx nuxi@latest module add shadcn-nuxt
</span></span><span class="line"><span class="cl">pnpx nuxi@latest module add tailwindcss
</span></span></code></pre></div><p>Initialize shadcn and tailwind -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">pnpx tailwindcss init
</span></span><span class="line"><span class="cl">pnpx shadcn-vue@latest init
</span></span></code></pre></div><p>Accept sensible defaults.</p>
<h2 id="step-2-configuring-nuxt">Step 2: Configuring Nuxt</h2>
<p>Ensure <code>nuxt.config.ts</code> is updated. Add the CSS file reference:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">// ...existing code...
</span></span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineNuxtConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...existing code...
</span></span></span><span class="line"><span class="cl">  <span class="nx">modules</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;shadcn-nuxt&#34;</span><span class="p">,</span> <span class="s2">&#34;@nuxt/icon&#34;</span><span class="p">,</span> <span class="s2">&#34;@nuxtjs/tailwindcss&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">css</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;~/assets/css/tailwind.css&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...existing code...
</span></span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Add shadcn-vue components.</p>
<pre tabindex="0"><code>pnpx shadcn-vue@latest add label button card input separator
</code></pre><h2 id="step-3-creating-the-document-uploader-component">Step 3: Creating the Document Uploader Component</h2>
<p>Create <code>DocumentUploader.vue</code> in the components folder to handle file uploads:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c1">// filepath: /c:/dev/1p/nuxt/docformist/components/DocumentUploader.vue
</span></span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!--</span> <span class="p">...</span><span class="nx">existing</span> <span class="nx">code</span><span class="p">...</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">emit</span> <span class="o">=</span> <span class="nx">defineEmits</span><span class="p">([</span><span class="s2">&#34;file-selected&#34;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">handleFileSelect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="o">:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">file</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span> <span class="nx">as</span> <span class="nx">HTMLInputElement</span><span class="p">).</span><span class="nx">files</span><span class="o">?</span><span class="p">.[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">emit</span><span class="p">(</span><span class="s2">&#34;file-selected&#34;</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">handleDrop</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="o">:</span> <span class="nx">DragEvent</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">file</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">dataTransfer</span><span class="o">?</span><span class="p">.</span><span class="nx">files</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">emit</span><span class="p">(</span><span class="s2">&#34;file-selected&#34;</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="step-4-creating-the-document-viewer-component">Step 4: Creating the Document Viewer Component</h2>
<p>Create <code>DocumentViewer.vue</code> to preview the uploaded document:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c1">// filepath: /c:/dev/1p/nuxt/docformist/components/DocumentViewer.vue
</span></span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!--</span> <span class="p">...</span><span class="nx">existing</span> <span class="nx">code</span><span class="p">...</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">props</span> <span class="o">=</span> <span class="nx">defineProps</span><span class="o">&lt;</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">file</span><span class="o">:</span> <span class="nx">File</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">fileUrl</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">string</span> <span class="err">|</span> <span class="na">null</span><span class="p">&gt;(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">isPDF</span> <span class="o">=</span> <span class="nx">computed</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nx">props</span><span class="p">.</span><span class="nx">file</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="s2">&#34;application/pdf&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">watch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">props</span><span class="p">.</span><span class="nx">file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nx">newFile</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">fileUrl</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">URL</span><span class="p">.</span><span class="nx">revokeObjectURL</span><span class="p">(</span><span class="nx">fileUrl</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fileUrl</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nx">createObjectURL</span><span class="p">(</span><span class="nx">newFile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span> <span class="nx">immediate</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">onBeforeUnmount</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">fileUrl</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">URL</span><span class="p">.</span><span class="nx">revokeObjectURL</span><span class="p">(</span><span class="nx">fileUrl</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="step-5-creating-the-api-endpoint">Step 5: Creating the API Endpoint</h2>
<p>Create an API endpoint <code>analyze.post.ts</code> for document analysis with Azure Document Intelligence:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">DocumentIntelligence</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">getLongRunningPoller</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isUnexpected</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@azure-rest/ai-document-intelligence&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">AzureKeyCredential</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@azure/core-auth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">formidable</span> <span class="kr">from</span> <span class="s2">&#34;formidable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">readFile</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;fs/promises&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineEventHandler</span><span class="p">(</span><span class="kr">async</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...existing code...
</span></span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><h2 id="step-6-building-the-main-page">Step 6: Building the Main Page</h2>
<p>Integrate the components and API in the main page <code>index.vue</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="c1">// filepath: /c:/dev/1p/nuxt/docformist/pages/index.vue
</span></span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!--</span> <span class="p">...</span><span class="nx">existing</span> <span class="nx">code</span><span class="p">...</span> <span class="o">--&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">selectedFile</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">File</span> <span class="err">|</span> <span class="na">null</span><span class="p">&gt;(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">extractedData</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">Record</span><span class="p">&lt;</span><span class="nt">string</span><span class="err">,</span> <span class="na">any</span><span class="p">&gt;</span> <span class="o">|</span> <span class="kc">null</span><span class="o">&gt;</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">loading</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">selectedFields</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">&lt;</span><span class="nt">string</span><span class="err">[]</span><span class="p">&gt;([]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">handleFileSelect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">file</span><span class="o">:</span> <span class="nx">File</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">selectedFile</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">file</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">extractedData</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">handleExtract</span> <span class="o">=</span> <span class="nx">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">selectedFields</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;No fields selected&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">formData</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;file&#34;</span><span class="p">,</span> <span class="nx">selectedFile</span><span class="p">.</span><span class="nx">value</span><span class="o">!</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">formData</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="s2">&#34;fields&#34;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">selectedFields</span><span class="p">.</span><span class="nx">value</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/api/analyze&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">method</span><span class="o">:</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">body</span><span class="o">:</span> <span class="nx">formData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="s2">&#34;Failed to analyze document&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">extractedData</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error analyzing document:&#34;</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// TODO: Show error toast
</span></span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">loading</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="step-7-run-the-application">Step 7: Run the Application</h2>
<p>Start the development server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm dev
</span></span></code></pre></div><p>Visit <code>http://localhost:3000</code> to upload a document and see the results.</p>
<h2 id="conclusion">Conclusion</h2>
<p>That was a quick demo of how we build a document analysis / identification app using Azure Document Intelligence. Customize the application further to meet your needs.</p>
<p>Here&rsquo;s the Github repo - <a href="https://github.com/prashanth1k/docformist">https://github.com/prashanth1k/docformist</a>.</p>
<p>All of this was done within 2 hours - thanks to AI. The era of personal applications is here.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Manage SVG Icons in Nuxt</title>
      <link>https://techformist.com/manage-svg-icons-images/</link>
      <pubDate>Sat, 20 Jan 2024 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/manage-svg-icons-images/</guid>
      <description>&lt;p&gt;Gone are the days when we depended on styling libraries to provide fast access to icons and go through untold suffering due to payload size, performance issues etc.. With the advent of SVG, we can use icons from a variety of sources. We will see how to do just that.&lt;/p&gt;
&lt;p&gt;SVG images have the advantage of -&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Being vector based, so it can be scaled to any size&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Is just text&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Can be styled&lt;/li&gt;
&lt;li&gt;Small in size!&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;using-svg-icons-why-do-that&#34;&gt;Using SVG Icons: Why do that?&lt;/h1&gt;
&lt;p&gt;You see in the old west, we used to have icons like this -&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Gone are the days when we depended on styling libraries to provide fast access to icons and go through untold suffering due to payload size, performance issues etc.. With the advent of SVG, we can use icons from a variety of sources. We will see how to do just that.</p>
<p>SVG images have the advantage of -</p>
<ol>
<li>Being vector based, so it can be scaled to any size</li>
<li>&ldquo;Is just text&rdquo;</li>
<li>Can be styled</li>
<li>Small in size!</li>
</ol>
<h1 id="using-svg-icons-why-do-that">Using SVG Icons: Why do that?</h1>
<p>You see in the old west, we used to have icons like this -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;fa fa-user&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>With libraries like Vuetify, the above statement looks like this -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">v-icon</span><span class="p">&gt;</span>mdi-account<span class="p">&lt;/</span><span class="nt">v-icon</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The code is easy to use, but you have to depend on the library to provide you the icon. You are also dependent on the library for applying changes to color, size, and probably even for the freaking dark mode.</p>
<p>With SVG, you can use the icon from any source, and apply any styling you want.</p>
<h1 id="using-svg-icons">Using SVG Icons</h1>
<p>Getting started with SVG icons is pretty easy.</p>
<ol>
<li>Identify the SVG</li>
<li>Copy the SVG and paste it directly in a Vue component (OR) Save the SVG in a file and import it in a Vue component</li>
</ol>
<p>As always the modern web makes using SVG icons trivial. 😜</p>
<h2 id="method-1-use-direct-links-to-icons">Method 1: Use direct links to icons</h2>
<p>You can use the SVG icon directly in your Vue component.</p>
<p>An example using <a href="https://icongr.am">icongram</a> ie below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">img</span>
</span></span><span class="line"><span class="cl">    <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://icongr.am/material/airplane-takeoff.svg?size=128&amp;color=4023d1&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The parameters are self-explanatory.
You can see the image, change the size / color and see how URLs are constructed for various icons from the <a href="https://icongr.am/material">icongram</a> website (linked to Material icons, it can be anything really).</p>
<p>Others doing the same fun stuff -</p>
<ul>
<li><a href="https://ionic.io/ionicons">Ionicons</a></li>
<li><a href="https://iconify.design/">Iconify</a></li>
<li><a href="https://css.gg/add">CSS icons</a> - icons are also available as SVG</li>
</ul>
<p>There is an easier way to use the icons directly.
Use something like a <a href="https://nuxt.com/modules/icon">NuxtIcon</a> and set it up as a plugin.</p>
<p>And, voila -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!-- ... --&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Icon</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;uil:github&#34;</span> <span class="na">color</span><span class="o">=</span><span class="s">&#34;black&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="c">&lt;!-- ... --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="method-2-download-and-use-icons">Method 2: Download and use icons</h2>
<p>Any of the sites outlined in the previous section allow you to download the SVG icon. Just save the file in your local public / assets folder and use it in your Vue component.</p>
<p>More example sites providing SVG downloads -</p>
<ul>
<li><a href="https://heroicons.com/">Heroicons</a></li>
<li><a href="https://feathericons.com/">Feather</a></li>
<li><a href="https://www.svgrepo.com/">SVG Repo</a></li>
<li><a href="https://material.io/resources/icons/?style=baseline">Material</a></li>
</ul>
<p>Once the images are available in your project, using them should be as simple as -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;images/airplane-takeoff.svg&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Another way to do this (probably more efficient) is to create a pass through Vue component that renders the SVG. This way you can play around with the size/colors etc. in a more uniform way.</p>
<p>Example Vue component to render a given SVG -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">svg</span>
</span></span><span class="line"><span class="cl">    <span class="na">:viewBox</span><span class="o">=</span><span class="s">&#34;viewBox&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">xmlns</span><span class="o">=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">:width</span><span class="o">=</span><span class="s">&#34;width&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">:height</span><span class="o">=</span><span class="s">&#34;height&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">class</span><span class="o">=</span><span class="s">&#34;svg-icon&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">import</span> <span class="p">{</span> <span class="nx">defineProps</span><span class="p">,</span> <span class="nx">computed</span><span class="p">,</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">defineProps</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">viewBox</span><span class="o">:</span> <span class="p">{</span> <span class="nx">type</span><span class="o">:</span> <span class="nb">String</span><span class="p">,</span> <span class="k">default</span><span class="o">:</span> <span class="s2">&#34;0 0 24 24&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">width</span><span class="o">:</span> <span class="p">{</span> <span class="nx">type</span><span class="o">:</span> <span class="nb">String</span><span class="p">,</span> <span class="k">default</span><span class="o">:</span> <span class="s2">&#34;24&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">height</span><span class="o">:</span> <span class="p">{</span> <span class="nx">type</span><span class="o">:</span> <span class="nb">String</span><span class="p">,</span> <span class="k">default</span><span class="o">:</span> <span class="s2">&#34;24&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Invoke the component as any other Vue component. This approach has the advantage of consistency in the way you render the SVG icons - e.g., use similar sizes, color, etc.</p>
<h2 id="method-3-just-paste-the-svg">Method 3: Just paste the SVG</h2>
<p>As an alternative to downloading icons, just paste the SVG in your Vue component.</p>
<p>For e.g. -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">svg</span>
</span></span><span class="line"><span class="cl">  <span class="na">data-slot</span><span class="o">=</span><span class="s">&#34;icon&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">fill</span><span class="o">=</span><span class="s">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">stroke-width</span><span class="o">=</span><span class="s">&#34;1.5&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">stroke</span><span class="o">=</span><span class="s">&#34;currentColor&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">viewBox</span><span class="o">=</span><span class="s">&#34;0 0 24 24&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">xmlns</span><span class="o">=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">aria-hidden</span><span class="o">=</span><span class="s">&#34;true&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">path</span>
</span></span><span class="line"><span class="cl">    <span class="na">stroke-linecap</span><span class="o">=</span><span class="s">&#34;round&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">stroke-linejoin</span><span class="o">=</span><span class="s">&#34;round&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">d</span><span class="o">=</span><span class="s">&#34;m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;&lt;/</span><span class="nt">path</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>As outlined in the previous section, you can also paste the SVG in a separate Vue component and use that component in your project.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Use Dynamic Images in Nuxt</title>
      <link>https://techformist.com/dynamic-images-nuxt/</link>
      <pubDate>Sun, 14 Jan 2024 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/dynamic-images-nuxt/</guid>
      <description>&lt;p&gt;First, let me clarify what I mean by &amp;ldquo;dynamic image&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Imagine the below situations -&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You are rendering a list of products and their images. The image may be stored in local folder or coming in from S3/comparable infrastructure&lt;/li&gt;
&lt;li&gt;You want to change image / make image interactive. For e.g., users select fruits and the corresponding fruit image gets added in a basket&lt;/li&gt;
&lt;li&gt;You want to preprocess the image in some shape and form at runtime. For e.g., watermarking the image&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;rendering-an-image&#34;&gt;Rendering an Image&lt;/h1&gt;
&lt;p&gt;Typically, you render images with -&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>First, let me clarify what I mean by &ldquo;dynamic image&rdquo;.</p>
<p>Imagine the below situations -</p>
<ol>
<li>You are rendering a list of products and their images. The image may be stored in local folder or coming in from S3/comparable infrastructure</li>
<li>You want to change image / make image interactive. For e.g., users select fruits and the corresponding fruit image gets added in a basket</li>
<li>You want to preprocess the image in some shape and form at runtime. For e.g., watermarking the image</li>
</ol>
<h1 id="rendering-an-image">Rendering an Image</h1>
<p>Typically, you render images with -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://example.com/images/apple.png&#34;</span> <span class="p">/&gt;</span>
</span></span></code></pre></div><p>This assumes that <code>images/apple.png</code> exists in <code>public</code> folder or available publicly.</p>
<h1 id="rendering-a-dynamic-image">Rendering a Dynamic Image</h1>
<p>While providing images from a public location is great (&amp; should be preferred), sometimes we need the images to be &ldquo;processed&rdquo; during build, or make it &ldquo;dynamic&rdquo; in some way or the other (e.g., watermarked on the go). I typically use <code>assets/images</code> for images that need not be publicly exposed - but any folder should work well.</p>
<p>Pointing images to the <code>assets</code> or some other folder is straight-forward in Nuxt2.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="o">&lt;</span><span class="nx">template</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="nx">img</span> <span class="o">:</span><span class="nx">src</span><span class="o">=</span><span class="s2">&#34;require(`~/assets/images/${apple.image}`)&#34;</span><span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="err">/template&gt;</span>
</span></span></code></pre></div><p><code>require</code> works since Nuxt2 uses Webpack, which will get the import image dynamically and make it available for rendering on the page.</p>
<p>Nuxt3 and Vite do not work in the same way. There is more than one way to render dynamic images in Nuxt 3.</p>
<h2 id="1-using-glob">1. Using <code>glob</code></h2>
<p>As mentioned <a href="https://github.com/nuxt/nuxt/issues/14766/#issuecomment-1397365205">here</a>, you can use <code>glob</code> to feed the right input to the image -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">img</span> <span class="na">:src</span><span class="o">=</span><span class="s">&#34;fruit_name&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">import</span> <span class="p">{</span> <span class="nx">filename</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;pathe/utils&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">glob</span> <span class="o">=</span> <span class="kr">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">glob</span><span class="p">(</span><span class="s1">&#39;~/assets/images/*.png&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">eager</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">images</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">glob</span><span class="p">).</span><span class="nx">map</span><span class="p">(([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="p">=&gt;</span> <span class="p">[</span><span class="nx">filename</span><span class="p">(</span><span class="nx">key</span><span class="p">),</span> <span class="nx">value</span><span class="p">.</span><span class="k">default</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">fruit_name</span> <span class="o">=</span> <span class="s1">&#39;apple&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>This option works, but is tiring to look at. There may also be performance issues if there are large number of images.</p>
<p>Note that the issue described here exists for local images. Remote images provide public URLs that can be rendered directly.</p>
<h4 id="2-use-vite-require-plugin">2. Use Vite require plugin</h4>
<p>Bring the usability of Webpack <code>require</code> to Vite using <a href="https://www.npmjs.com/package/vite-plugin-require">vite-plugin-require</a> plugin.</p>
<p>Install the plugin -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -D vite-require-context
</span></span></code></pre></div><p>Change <code>vite.config.js</code> to add the plugin -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">vitePluginRequire</span> <span class="nx">from</span> <span class="s2">&#34;vite-plugin-require&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="nx">vitePluginRequire</span><span class="p">()],</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Nuxt (specifically <code>NuxtImg</code>) provides a helpful <code>useImage</code> composable.</p>
<p>You should now be able to do -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">NuxtImg</span> <span class="na">:src</span><span class="o">=</span><span class="s">&#34;require(fruit_image_url)&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">fruit_image_url</span> <span class="o">=</span> <span class="s2">&#34;~/assets/images/apple.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h1 id="conclusion">Conclusion</h1>
<p>While both options work, do note that -</p>
<ol>
<li>Use <code>public</code> for images that will not change. This option is easier and likely to be more performant</li>
<li>Else, use one of the above options based on your use case</li>
</ol>
]]></content:encoded>
    </item>
    
    <item>
      <title>Nuxt/Vue and Next/React - A Predicament</title>
      <link>https://techformist.com/nuxt-next-predicament/</link>
      <pubDate>Sun, 03 Dec 2023 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/nuxt-next-predicament/</guid>
      <description>&lt;p&gt;I have a full-time job that does not involve active web development. I am, however, on the constant look-out for the next big thing that will solve all my life&amp;rsquo;s problems.., by quickly creating apps that is.&lt;/p&gt;
&lt;p&gt;Vue has always been a tool that I depend on. Vue is quite easy to understand, use and build apps with. Nuxt just makes it even easier.&lt;/p&gt;
&lt;p&gt;I found React to be too verbose, generally more complicated, and not fun to work with. With &lt;a href=&#34;https://nextjs.org/&#34;&gt;NextJS&lt;/a&gt; 14, React Server Components, and the ** huge ** number of NextJS templates out there (not to mention &lt;a href=&#34;https://v0.dev&#34;&gt;v0.dev&lt;/a&gt;), I could kinda see how my workflow could be so much more easier.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have a full-time job that does not involve active web development. I am, however, on the constant look-out for the next big thing that will solve all my life&rsquo;s problems.., by quickly creating apps that is.</p>
<p>Vue has always been a tool that I depend on. Vue is quite easy to understand, use and build apps with. Nuxt just makes it even easier.</p>
<p>I found React to be too verbose, generally more complicated, and not fun to work with. With <a href="https://nextjs.org/">NextJS</a> 14, React Server Components, and the ** huge ** number of NextJS templates out there (not to mention <a href="https://v0.dev">v0.dev</a>), I could kinda see how my workflow could be so much more easier.</p>
<p>The big question though was - &ldquo;does Next+React really deliver the goods for me&rdquo;. And thus began a long time &ldquo;investing&rdquo; effort to find out what should be my stack of choice for the foreseeable future.</p>
<p>tldr;
It does not matter. Stick to the tooling that you like.</p>
<p>Before we move forward, for those of you who want all the other libraries &amp; frameworks considered - yes, I do code in them from time to time. No, they cannot be my go-to choice since I need to create things quickly lest I lose my interest and having lot of people out there simply means I can look forward to more answered questions and getting away with my scrappy &ldquo;expertise&rdquo;.</p>
<h1 id="the-need">The Need</h1>
<p>I am largely interested in building applications that help organize data, and provide ways to get things done in general. While not strictly focused on enterprises, they will border on things like -</p>
<ol>
<li>CRM/ERP&rsquo;s of the world</li>
<li>Project organization</li>
<li>People and task management for specific focus areas like recruitment, vendor management, etc.</li>
</ol>
<p>My typical apps have -</p>
<ol>
<li>Forms</li>
<li>Data tables</li>
<li>A few charts, reports and such</li>
</ol>
<p>Indirectly, I need -</p>
<ol>
<li>Ability to switch b/w real-time and classic fetching of updates</li>
<li>Easier state management</li>
<li>Ability to create a lot of reusable components</li>
<li>Ability to integrate with many libraries that can integrate well with documents, tables, reports and so forth</li>
</ol>
<h1 id="the-comparison-factors">The Comparison Factors</h1>
<p>Given the need is so generic and seem straight-forward at the outset, it is easy to get carried away. I decided to focus on a few seemingly random factors that I find useful or problematic.</p>
<ol>
<li><a href="#component-structure">Component Structure</a></li>
<li><a href="#server-side-rendering">Server-Side Rendering &amp; Data Fetching</a></li>
<li><a href="#state-management">State Management</a></li>
<li><a href="#performance">Performance</a></li>
<li><a href="#developer-experience">Developer Experience</a></li>
</ol>
<h1 id="component-structure">Component Structure</h1>
<p>Components are building blocks of any app. They are the ones that make up the UI and the logic behind it.</p>
<p>Creating components in Vue has been a breeze. The single file component structure is a joy to work with.</p>
<ul>
<li>Everything is neat and organized - from code, to styles, to the HTML</li>
<li>The separation of concerns is easy to manage</li>
<li>Code for reactive UI looks intuitive and easy to understand</li>
<li>Reusable code can be managed through Components and Services. Auto imports in Nuxt make it even easier to use them</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span> <span class="p">{{</span> <span class="nx">title</span> <span class="p">}}</span> <span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="s1">&#39;Hello World&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>React is not far off from here -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="o">=</span><span class="s">&#34;App&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span><span class="nx">Hello</span> <span class="nx">World</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>A somewhat more complexified version can be -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">TodoList</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">items</span><span class="p">,</span> <span class="nx">setItems</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">newItem</span><span class="p">,</span> <span class="nx">setNewItem</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">addItem</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setItems</span><span class="p">([...</span><span class="nx">items</span><span class="p">,</span> <span class="nx">newItem</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setNewItem</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span> <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">newItem</span><span class="p">}</span> <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="p">=&gt;</span> <span class="nx">setNewItem</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">addItem</span><span class="p">}&gt;</span><span class="nx">Add</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nx">items</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">index</span><span class="p">}&gt;{</span><span class="nx">item</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">))}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Vue still manages to look clean -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;newItem&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="nt">@click</span><span class="s">=&#34;addItem&#34;</span><span class="p">&gt;</span><span class="na">Add</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">li</span> <span class="nt">v-for</span><span class="o">=</span><span class="s">&#34;(item, index) in items&#34; :key=&#34;index&#34;</span><span class="p">&gt;{{</span> <span class="na">item</span> <span class="p">}}&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ref</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">items</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">newItem</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">addItem</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">items</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">newItem</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">newItem</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>I had a hard preference for Vue structure (think Options API), but not any more.</p>
<ul>
<li>Reactivity is easy to write &amp; manage in Vue. Just use <code>ref</code> / <code>reactive</code> and you are good to go.</li>
<li>Intuitive code in templates and script. Cleaner separation of concerns</li>
<li>Bidirectional data binding is a breeze</li>
<li>Template helpers like <code>v-for</code>, <code>v-if</code> are a God-send</li>
</ul>
<h1 id="server-side-rendering--data-fetching">Server-Side Rendering &amp; Data Fetching</h1>
<p>React Server Components make it easier to write server-side code and have the right mix of client and server logic in the same component.</p>
<p>For example, I could fetch <code>posts</code> data from server and send the HTML to the client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useEffect</span><span class="p">,</span> <span class="nx">useState</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">PostList</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">posts</span><span class="p">,</span> <span class="nx">setPosts</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">useEffect</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;https://jsonplaceholder.typicode.com/posts&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">response</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">data</span> <span class="p">=&gt;</span> <span class="nx">setPosts</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">posts</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Loading</span><span class="p">...&lt;/</span><span class="nt">div</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">posts</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">post</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;{</span><span class="nx">post</span><span class="p">.</span><span class="nx">title</span><span class="p">}&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">post</span><span class="p">.</span><span class="nx">body</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">))}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>No haggling around with API calls, no moving around JS to get basic things done, and more control over how/who fetches data.</p>
<p>If I use Nuxt for the same problem, here&rsquo;s how the component looks like -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span><span class="o">:</span> <span class="nx">posts</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">useAsyncData</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nx">$fetch</span><span class="p">(</span><span class="s1">&#39;https://jsonplaceholder.typicode.com/posts&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-for</span><span class="o">=</span><span class="s">&#34;post in posts&#34; :key=&#34;post&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{{</span> <span class="na">post</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>See <a href="https://nuxt.com/docs/guide/concepts/rendering">Nuxt SSR</a>.</p>
<p>RSC is a hard win for React. Next components are server by default, and mixing server / client components without a care for the world is enticing. While Nuxt makes it easy to build data fetching on top of Vue, it just does not compare to the magic of RSC.</p>
<p>That said, I really love the Vue way of doing things -</p>
<ul>
<li>don&rsquo;t worry about <code>useEffect</code> and other thousand hooks</li>
<li>no easy foot-guns causing performance issues or code spaghetti</li>
<li>clean, easy to understand template</li>
</ul>
<p>Many other SSR and data fetching features exist, but are not super important for me.</p>
<h1 id="state-management">State Management</h1>
<p>Using state management in React can be tricky. There are many ways to do it, and each has its own pros and cons. Let us see an example of a simple todo app using Zustland.</p>
<p>The component will look something like this -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">useStore</span> <span class="nx">from</span> <span class="s1">&#39;./store&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">TodoApp</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="nx">useStore</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">todos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">addTodo</span> <span class="o">=</span> <span class="nx">useStore</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">addTodo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">toggleTodo</span> <span class="o">=</span> <span class="nx">useStore</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">toggleTodo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">removeTodo</span> <span class="o">=</span> <span class="nx">useStore</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="nx">state</span><span class="p">.</span><span class="nx">removeTodo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">newTodo</span><span class="p">,</span> <span class="nx">setNewTodo</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">handleAddTodo</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTodo</span><span class="p">(</span><span class="nx">newTodo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setNewTodo</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">input</span> <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">newTodo</span><span class="p">}</span> <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="p">=&gt;</span> <span class="nx">setNewTodo</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">handleAddTodo</span><span class="p">}&gt;</span><span class="nx">Add</span> <span class="nx">Todo</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="nx">todos</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">index</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;checkbox&#34;</span> <span class="na">checked</span><span class="o">=</span><span class="p">{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">completed</span><span class="p">}</span> <span class="na">onChange</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">toggleTodo</span><span class="p">(</span><span class="nx">index</span><span class="p">)}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">removeTodo</span><span class="p">(</span><span class="nx">index</span><span class="p">)}&gt;</span><span class="nx">Remove</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">))}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">TodoApp</span><span class="p">;</span>
</span></span></code></pre></div><p>.. and the store -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">create</span> <span class="nx">from</span> <span class="s1">&#39;zustand&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">useStore</span> <span class="o">=</span> <span class="nx">create</span><span class="p">(</span><span class="nx">set</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">todos</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">set</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="p">({</span> <span class="nx">todos</span><span class="o">:</span> <span class="p">[...</span><span class="nx">state</span><span class="p">.</span><span class="nx">todos</span><span class="p">,</span> <span class="p">{</span> <span class="nx">text</span><span class="p">,</span> <span class="nx">completed</span><span class="o">:</span> <span class="kc">false</span> <span class="p">}]</span> <span class="p">})),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">removeTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">set</span><span class="p">(</span><span class="nx">state</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">newTodos</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">state</span><span class="p">.</span><span class="nx">todos</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="nx">newTodos</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="nx">index</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span> <span class="nx">todos</span><span class="o">:</span> <span class="nx">newTodos</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}),</span>
</span></span><span class="line"><span class="cl"><span class="p">}));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">useStore</span><span class="p">;</span>
</span></span></code></pre></div><p>In the Vue world, Pina (or even Nuxt state mgt.) is quite easy to use.
The component is simpler but comparable -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">input</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;newTodo&#34; type=&#34;text&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">button</span> <span class="nt">@click</span><span class="s">=&#34;addTodo&#34;</span><span class="p">&gt;</span><span class="na">Add</span> <span class="na">Todo</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">li</span> <span class="nt">v-for</span><span class="o">=</span><span class="s">&#34;(todo, index) in todos&#34; :key=&#34;index&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{{</span> <span class="na">todo.text</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">button</span> <span class="nt">@click</span><span class="s">=&#34;removeTodo(index)&#34;</span><span class="p">&gt;</span><span class="na">Remove</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ref</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./store&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setup</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">useStore</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">newTodo</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">addTodo</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">store</span><span class="p">.</span><span class="nx">addTodo</span><span class="p">(</span><span class="nx">newTodo</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">newTodo</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">todos</span><span class="o">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">todos</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">newTodo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">addTodo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">removeTodo</span><span class="o">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">removeTodo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Here&rsquo;s how the store looks like -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;pinia&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">useStore</span> <span class="o">=</span> <span class="nx">defineStore</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span><span class="o">:</span> <span class="s1">&#39;todos&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">state</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">todos</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">  <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">actions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTodo</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="p">.</span><span class="nx">todos</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="nx">text</span><span class="p">,</span> <span class="nx">completed</span><span class="o">:</span> <span class="kc">false</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">removeTodo</span><span class="p">(</span><span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="p">.</span><span class="nx">todos</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="nx">index</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>I find Vue easier to use for arrays, object processing, and that trend continues for stores. But, I can totally understand how majority of the people out there feel as strong about React.</p>
<h1 id="performance">Performance</h1>
<p>Performance has not been a straight-forward and direct aspect of choosing a frontend framework. Most of the apps I build use database, data fetching from multiple sources, and components like maps/ charts - all of which shift performance considerations elsewhere.</p>
<p>I tend to rely on and follow the benchmarks published by talented people out there.</p>
<ul>
<li><a href="https://krausest.github.io/js-framework-benchmark/current.html">krausest/js-framework-benchmark</a></li>
<li>.. or more theoritical exercises like <a href="https://helda.helsinki.fi/server/api/core/bitstreams/a301a028-2441-41d2-b13e-e6ab8aa6e4d5/content">this</a></li>
</ul>
<p>There are not going to be any clear answers here, but Vue and React perform on par with each other.</p>
<h1 id="developer-experience">Developer Experience</h1>
<p>In general I am strongly biased towards the Vue way of working.</p>
<p>I have tried to change my ways multiple times - for React, Blazor, Astro, HTMX and so forth - but keep picking up Nuxt/Vue for real projects. This may be more of a reflection of my own laziness than anything else.</p>
<p>I do like the fact that -</p>
<ol>
<li>Vue is easy to learn and use</li>
<li>Nuxt makes it quite easy to build apps</li>
<li>Has a decent ecosystem of libraries and tools</li>
</ol>
<p>React and NextJS have always been the tools that I will certainly choose in the &ldquo;next&rdquo; project.</p>
<ul>
<li>A larger community implies many more people answering questions that an amateur like me tends to have</li>
<li>Many starter templates - including a few that are targeted at SaaS apps</li>
<li>More AI generator friendly since they are used by more people and have many more repositories available publicly. However, React Server Components and the frequent NextJS changes somewhat blunt that advantage</li>
<li>v0.dev is a significant advantage too</li>
<li><code>Framework of frameworks</code> like RedwoodJS, BlitzJS help stitch together many other tools and libraries to create a full-stack app</li>
<li>Styling libraries like Chakra UI, mui seem to be far more popular than their Vue counterparts</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>There are no significant advantages or limitations of using either of the options.</p>
<p>I see my own Vue + Nuxt bias, which has nothing to do with the tools themselves. While I continue to use Vue, I will have a keen eye on how React Server Components evolve and how Vue Vapor potentially changes the game.</p>
<p>If you are new to the ecosystem, build a few apps with either and see which option you like more. You may find yourself using both at one time or the other - so don&rsquo;t get too bogged down by the choices.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Building a Todo App using Nuxt, PrimeVue and SQLite</title>
      <link>https://techformist.com/todo-app-nuxt-primevue-sqlite/</link>
      <pubDate>Sat, 14 Oct 2023 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/todo-app-nuxt-primevue-sqlite/</guid>
      <description>&lt;p&gt;Let us create a simple Todo app using Nuxt -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PrimeVue for styling&lt;/li&gt;
&lt;li&gt;Store todos in a database&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;setup&#34;&gt;Setup&lt;/h1&gt;
&lt;p&gt;Ensure NodeJS is installed. Download and follow instructions &lt;a href=&#34;https://nodejs.org/en/download/&#34;&gt;here&lt;/a&gt; if you don&amp;rsquo;t have Node installed on your computer. I use pnpm as my package manager. You can use npm or yarn if you prefer. Install pnpm globally using the following command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm install -g pnpm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Create a new &lt;a href=&#34;https://nuxtjs.org/docs/get-started/installation&#34;&gt;Nuxt&lt;/a&gt; project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pnpx nuxi@latest init nuxt-primevue-todo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command should initiate a project, install dependencies and also prompt you to initiate a git repository.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Let us create a simple Todo app using Nuxt -</p>
<ul>
<li>PrimeVue for styling</li>
<li>Store todos in a database</li>
</ul>
<h1 id="setup">Setup</h1>
<p>Ensure NodeJS is installed. Download and follow instructions <a href="https://nodejs.org/en/download/">here</a> if you don&rsquo;t have Node installed on your computer. I use pnpm as my package manager. You can use npm or yarn if you prefer. Install pnpm globally using the following command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -g pnpm
</span></span></code></pre></div><p>Create a new <a href="https://nuxtjs.org/docs/get-started/installation">Nuxt</a> project.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpx nuxi@latest init nuxt-primevue-todo
</span></span></code></pre></div><p>This command should initiate a project, install dependencies and also prompt you to initiate a git repository.</p>
<p>Next, install <a href="https://primevue.org/installation">PrimeVue</a>..</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm install primevue
</span></span></code></pre></div><p>..and follow instructions to enable <a href="https://primevue.org/installation/#nuxtintegration">PrimeVue for Nuxt</a>.</p>
<p>While at it, let us also install a few additional libraries that will help with styling.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm add primeflex primeicons
</span></span></code></pre></div><p>Add below content in <code>nuxt.config.js</code> to enable PrimeVue.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineNuxtConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="nx">css</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;public/themes/light/theme.css&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;~/assets/styles.scss&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;primeicons/primeicons.css&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;primeflex/primeflex.css&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">build</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">transpile</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;primevue&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="nx">modules</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;nuxt-primevue&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="nx">primevue</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">usePrimeVue</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">components</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">prefix</span><span class="o">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">name</span><span class="o">:</span> <span class="kc">undefined</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">include</span><span class="o">:</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">exclude</span><span class="o">:</span> <span class="kc">undefined</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>Note that we have included all primevue components in the <code>components</code> section. Typically you use <code>include</code> and <code>exclude</code> to be selective about the components to be included for the project.</p>
<p>Next, we will include some custom styling. Since I am lazy, I will get inspired by a free template provided by PrimeVue called <a href="https://github.com/primefaces/sakai-vue/">SakaiVue</a></p>
<p>Create a file <code>assets/styles.scss</code> -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;layout/layout.scss&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>Create a new file <code>styles/layout/layout.scss</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_variables&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_mixins&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_preloading&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_main&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_topbar&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_config&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_content&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_footer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_responsive&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_utils&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;./_typography&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>The individual files will have some custom styling elements - you will find them in the <a href="https://github.com/prashanth1k/nuxt-primevue-todo">repo here</a>. Again, all the circus with styling is not quite necessary for the simple app we are building, but is simply included to force a structured approach.</p>
<p>Start server with the command <code>pnpm run dev</code> and you should see the following screen.</p>
<p>
    <img loading="lazy" src="/static/2023/nuxt-starting-up-todoapp.png" alt="Nuxt Starting up with PrimeVue"  /></p>
<h1 id="home-page--navigation">Home Page &amp; Navigation</h1>
<p>Add a navigation bar to the app. Create a new file <code>components/NavbarAnon.vue</code> and add below content. I used <code>Anon</code> suffix to denote that the toolbar is for anonymous user - I have found it easier to have a separate toolbar for logged-in users (not quite relevant here).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Toolbar</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;layout-topbar&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">template</span> <span class="err">#</span><span class="na">start</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">NuxtLink</span> <span class="na">to</span><span class="o">=</span><span class="s">&#34;/&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">NuxtImg</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#34;logo&#34;</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;../assets/images/todo-logo.png&#34;</span> <span class="na">sizes</span><span class="o">=</span><span class="s">&#34;150vw sm:100vw md:200vw&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mr-2&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">NuxtLink</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">template</span> <span class="err">#</span><span class="na">end</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pi pi-question-circle&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Toolbar</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Toolbar</span> <span class="nx">from</span> <span class="s1">&#39;primevue/toolbar&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Create a default layout for the app with a new file <code>layouts/default.vue</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">NuxtLoadingIndicator</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">NavbarAnon</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="na">layout</span><span class="nt">-main-container</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="na">layout</span><span class="nt">-main</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">slot</span><span class="p">&gt;&lt;/</span><span class="nt">slot</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Footer</span><span class="p">&gt;&lt;/</span><span class="nt">Footer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Create the home page <code>pages/index.vue</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">Hero</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12 mt-12&#34;</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;features&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">Features</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12 mt-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;surface-section px-4 py-8 md:px-6 lg:px-8&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-700 text-center&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-900 font-bold text-5xl mb-3&#34;</span><span class="p">&gt;</span><span class="nx">What</span> <span class="nx">are</span> <span class="nx">you</span> <span class="nx">waiting</span> <span class="k">for</span><span class="o">?</span> <span class="nx">It</span><span class="err">&#39;</span><span class="nx">s</span> <span class="nx">free</span> <span class="k">for</span> <span class="nx">individual</span> <span class="nx">use</span><span class="o">!</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-700 text-2xl mb-5&#34;</span><span class="p">&gt;</span><span class="nx">Experience</span> <span class="nx">the</span> <span class="nx">task</span> <span class="nx">organizing</span> <span class="kr">super</span> <span class="nx">power</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">label</span><span class="o">=</span><span class="s">&#34;Sign up&#34;</span> <span class="na">icon</span><span class="o">=</span><span class="s">&#34;pi pi-discord&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">class</span><span class="o">=</span><span class="s">&#34;font-bold px-5 py-3 p-button-raised p-button-rounded white-space-nowrap&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The page will use the <code>default</code> layout. The individual components are not much to speak about - you will find them in the <a href="https://github.com/prashanth1k/nuxt-primevue-todo">repo</a>.</p>
<p>You should now see a nice looking home page at <code>http://localhost:3000</code>.</p>
<p><img loading="lazy" src="/2023/nuxt-primevue-home-page.png" type="" alt="nuxt-primevue-home-page"  /></p>
<p>I did include a second layout for the logged-in user in the repo, but am not discussing that here (useful in cases where I need a different toolbar, sidebar navigation for the actual app, for example).</p>
<h1 id="setup-drizzle--data-migrations">Setup Drizzle &amp; Data Migrations</h1>
<p>Let us setup Drizzle ORM and Drizzle Kit. The latter is used for migrations and other admin tasks.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm i drizzle-orm better-sqlite3
</span></span><span class="line"><span class="cl">pnpm i -D drizzle-kit
</span></span></code></pre></div><p>Create a new file <code>drizzle.config.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">schema</span><span class="o">:</span> <span class="s2">&#34;./data/schema_*.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">out</span><span class="o">:</span> <span class="s2">&#34;./data/migrations&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">driver</span><span class="o">:</span> <span class="s2">&#34;better-sqlite&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">dbCredentials</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">url</span><span class="o">:</span> <span class="s2">&#34;./data/data.db&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>The schema file is going to be used by default by Drizzle CLI.  While we do the actual migrations, all we need to do is <code>pnpm exec drizzle-kit generate:sqlite</code> and the migration files are automatically created in the <code>out</code> folder. We will see this in action in a second.</p>
<p>First, let us create a simple table. Create a new file <code>./data/schema_user.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">sqliteTable</span><span class="p">,</span> <span class="nx">integer</span><span class="p">,</span> <span class="nx">text</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm/sqlite-core&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">sql</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">sqliteTable</span><span class="p">(</span><span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span><span class="o">:</span> <span class="nx">integer</span><span class="p">(</span><span class="s2">&#34;id&#34;</span><span class="p">).</span><span class="nx">primaryKey</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">firstName</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;first_name&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">lastName</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;last_name&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">email</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">).</span><span class="nx">unique</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">created</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;created&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="nx">sql</span><span class="sb">`CURRENT_TIMESTAMP`</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">updated</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;updated&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="nx">sql</span><span class="sb">`CURRENT_TIMESTAMP`</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Let us also create the table for our todos. Create a new file <code>./data/schema_todo.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">sqliteTable</span><span class="p">,</span> <span class="nx">integer</span><span class="p">,</span> <span class="nx">text</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm/sqlite-core&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">sql</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">todo</span> <span class="o">=</span> <span class="nx">sqliteTable</span><span class="p">(</span><span class="s2">&#34;todo&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">id</span><span class="o">:</span> <span class="nx">integer</span><span class="p">(</span><span class="s2">&#34;id&#34;</span><span class="p">).</span><span class="nx">primaryKey</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">firstName</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;title&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">lastName</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;status&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">created</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;created&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="nx">sql</span><span class="sb">`CURRENT_TIMESTAMP`</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">updated</span><span class="o">:</span> <span class="nx">text</span><span class="p">(</span><span class="s2">&#34;updated&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">(</span><span class="nx">sql</span><span class="sb">`CURRENT_TIMESTAMP`</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Create the migration file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm <span class="nb">exec</span> drizzle-kit generate:sqlite
</span></span></code></pre></div><p>You could also suffix <code>--out data/migrations --schema data/schema_user.js</code> if you want to be explicit about the output folder and schema file.</p>
<p>Run the migration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpm <span class="nb">exec</span> drizzle-kit push:sqlite
</span></span></code></pre></div><p>You can see browse the SQLite database and see the table created using the Drizzle tool (<code>pnpm exec drizzle-kit studio</code> &amp; point the browser at the URL), or using a tool like <a href="https://sqlitebrowser.org/">DB Browser for SQLite</a>.</p>
<h1 id="creating-the-server-api">Creating the Server API</h1>
<p>Nuxt is great for the frontend, but also features a full-fledged node server / framework based on <a href="https://github.com/unjs/h3">h3</a>.</p>
<p>We will use this server to create a test API.</p>
<p>Create a new file <code>server/api/hello.js</code> with below content -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineEventHandler</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">{</span> <span class="nx">message</span><span class="o">:</span> <span class="s2">&#34;Hello world!&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>You can also use the <code>nuxi</code> command to <a href="https://nuxt.com/docs/api/commands/add#nuxi-add-api">generate the <code>api</code></a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pnpx nuxi generate api hello
</span></span></code></pre></div><p>When your Nuxt server is running, you can hit this route at <code>http://localhost:3000/api/hello</code> to see the response.</p>
<p>Now that we know the server works, let us create our first API. Create a new file <code>server/api/user-exists.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">user</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/data/schema_user&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">db</span><span class="p">,</span> <span class="nx">sqlite</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;../dbservice&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">eq</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineEventHandler</span><span class="p">(</span><span class="kr">async</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">readBody</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Initiated user find. Got body: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">body</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34; and search param: &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">body</span><span class="o">?</span><span class="p">.</span><span class="nx">email</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">resp</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">select</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span> <span class="nx">body</span><span class="o">?</span><span class="p">.</span><span class="nx">email</span> <span class="o">||</span> <span class="s2">&#34;&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">get</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Search results:&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">resp</span><span class="o">?</span><span class="p">.</span><span class="nx">id</span> <span class="o">!=</span> <span class="kc">undefined</span><span class="p">)</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">userExists</span><span class="o">:</span> <span class="kc">true</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span> <span class="k">return</span> <span class="p">{</span> <span class="nx">userExists</span><span class="o">:</span> <span class="kc">false</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error: &#34;</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">createError</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>The code is self explanatory -</p>
<ol>
<li>We collect the <code>body</code> from incoming message to get email</li>
<li>Search for the user in the database with Drizzle. <code>dbservice</code> is a simple wrapper around Drizzle to make the connections and queries simpler</li>
<li>Return a response with <code>userExists</code> flag</li>
</ol>
<p>Create a new file <code>server/dbservice.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">drizzle</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm/better-sqlite3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Database</span> <span class="nx">from</span> <span class="s2">&#34;better-sqlite3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">sqlite</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Database</span><span class="p">(</span><span class="s2">&#34;data/data.db&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">drizzle</span><span class="p">(</span><span class="nx">sqlite</span><span class="p">);</span>
</span></span></code></pre></div><p>Use your favourite REST client to test the API. Pass in a JSON body with <code>email</code> and you should see a response with <code>userExists</code> flag.</p>
<p>Input -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;a@a.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Output -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;userExists&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h1 id="add-user-registrationlogin-page">Add User Registration/Login Page</h1>
<p>Create a new file <code>pages/signup.vue</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">password</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">userExists</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">checkUserExists</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">useFetch</span><span class="p">(</span><span class="s1">&#39;/api/user-exists&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;POST&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">email</span><span class="o">:</span> <span class="nx">email</span><span class="p">.</span><span class="nx">value</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;checkUserExists data: &#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">userExists</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">userExists</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">createUser</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">useFetch</span><span class="p">(</span><span class="s1">&#39;/api/user-create&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;POST&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">email</span><span class="o">:</span> <span class="nx">email</span><span class="p">.</span><span class="nx">value</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;createUser data: &#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">userExists</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">userExists</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">await</span> <span class="nx">navigateTo</span><span class="p">(</span><span class="s1">&#39;/dashboard&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;surface-ground flex align-items-center justify-content-center &#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex flex-column align-items-center justify-content-center&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">data</span><span class="o">:</span><span class="p">{{</span> <span class="nx">data</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;../assets/images/todo-logo.png&#34;</span> <span class="na">alt</span><span class="o">=</span><span class="s">&#34;todo app&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mb-5 w-6rem flex-shrink-0&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">                <span class="na">style</span><span class="o">=</span><span class="s">&#34;border-radius: 56px; padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-full surface-card py-8 px-5 sm:px-8&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;border-radius: 53px&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-center mb-5&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;pi pi-user&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;font-size: 2.5rem&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-900 text-3xl font-medium mb-3&#34;</span><span class="p">&gt;</span><span class="nx">Hello</span> <span class="nx">Chief</span><span class="o">!</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-600 font-medium&#34;</span><span class="p">&gt;</span><span class="nx">Are</span> <span class="nx">you</span> <span class="nx">ready</span> <span class="nx">to</span> <span class="nx">get</span> <span class="nx">organized</span><span class="o">?</span><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;email1&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;block text-900 text-xl font-medium mb-2&#34;</span><span class="p">&gt;</span><span class="nx">Email</span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">InputText</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;email1&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text&#34;</span> <span class="na">placeholder</span><span class="o">=</span><span class="s">&#34;Email address&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-full md:w-30rem&#34;</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;email&#34; @change=&#34;checkUserExists&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex align-items-center justify-content-between mb-5 gap-5&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">&lt;</span><span class="nt">small</span><span class="p">&gt;</span><span class="nx">Login</span> <span class="k">if</span> <span class="nx">your</span> <span class="nx">user</span><span class="o">/</span><span class="nx">email</span> <span class="nx">id</span> <span class="nx">is</span> <span class="nx">found</span><span class="p">.</span> <span class="nx">Else</span><span class="p">,</span> <span class="nx">just</span> <span class="nx">create</span> <span class="nx">one</span><span class="o">!</span><span class="p">&lt;/</span><span class="nt">small</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;password1&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;block text-900 font-medium text-xl mb-2&#34;</span>
</span></span><span class="line"><span class="cl">                            <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;userExists&#34;</span><span class="p">&gt;</span><span class="na">Password</span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">Password</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;password1&#34;</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;password&#34; placeholder=&#34;Password&#34; :toggleMask=&#34;true&#34;</span><span class="p">
</span></span></span><span class="line"><span class="cl">                            <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-full mb-3&#34;</span> <span class="na">inputClass</span><span class="o">=</span><span class="s">&#34;w-full&#34;</span> <span class="o">:</span><span class="na">inputStyle</span><span class="o">=</span><span class="s">&#34;{ padding: &#39;1rem&#39; }&#34;</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;userExists&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;/</span><span class="nt">Password</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">label</span><span class="o">=</span><span class="s">&#34;Signup / Login&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;w-full p-3 text-xl&#34;</span> <span class="nt">@click</span><span class="s">=&#34;createUser&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>You now have a registration / login page.</p>
<p><img loading="lazy" src="/2023/nuxt-primevue-signup-page.png" type="" alt="nuxt-primevue-signup-page"  /></p>
<p>Go ahead and enter any email id and you are registered! We will not be adding a full-fledged login function in this post (we need to use some sort of <code>auth</code> library like <a href="https://lucia-auth.com/guidebook/github-oauth/nuxt/">Lucia</a> to do that).</p>
<p>You may be wondering on why registration and login functions are combined and not two separate pages. You cannot go wrong with either, but the common page is advantageous when you support different types of authentication (e.g., Google, Facebook, custom etc.).</p>
<h1 id="add-todo-api">Add Todo API</h1>
<p>Create two new APIs to handle <code>get</code> and <code>post</code>. We will use &ldquo;proper&rdquo; conventions this time around.</p>
<p>First, create a new file <code>server/api/todo.get.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">todo</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/data/schema_todo&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">db</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;../dbservice&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineEventHandler</span><span class="p">(</span><span class="kr">async</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">resp</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">select</span><span class="p">().</span><span class="nx">from</span><span class="p">(</span><span class="nx">todo</span><span class="p">).</span><span class="nx">all</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Search results - todo:&#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">resp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error: &#34;</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">createError</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p><code>todo.get.js</code> will handle requests to get all todo&rsquo;s. If there was a need to get a specific todo, I would create a similar file <code>server/api/[id].get.js</code> and handle the request there.</p>
<p>Let us also code to handle <code>post</code> - create a new file <code>server/api/todo.post.js</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">todo</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/data/schema_todo&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">db</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;../dbservice&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">eq</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;drizzle-orm&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineEventHandler</span><span class="p">(</span><span class="kr">async</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">readBody</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;todo body: &#34;</span><span class="p">,</span> <span class="nx">body</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">newTodo</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">body</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">resp</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">todo</span><span class="p">).</span><span class="nx">values</span><span class="p">(</span><span class="nx">newTodo</span><span class="p">).</span><span class="nx">run</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;inserted todo: &#34;</span><span class="p">,</span> <span class="nx">resp</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">todoResp</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">select</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nx">todo</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">where</span><span class="p">(</span><span class="nx">eq</span><span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">resp</span><span class="o">?</span><span class="p">.</span><span class="nx">lastInsertRowid</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">get</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">todoResp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error: &#34;</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">createError</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="nx">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Key points to note -</p>
<ol>
<li><code>const newTodo = { ...body }</code> may be useless in this situation, but is more relevant when using types or performing validations on the incoming message</li>
<li>The <code>insert</code> function returns a response with <code>lastInsertRowid</code> - we use that to get the newly created todo and return it to the caller. Calling function in client will use the data to update the UI. Again, the relevance may be lost here, but is a good practice since database commit may populate fields, add timestamps etc. and the client needs the updated info</li>
</ol>
<h1 id="add-todo-page">Add Todo Page</h1>
<p>Create a new file <code>pages/dashboard.vue</code> and add below content.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">setup</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ref</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;vue&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="nx">reactive</span><span class="p">({});</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="nx">ref</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">getTodos</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">useFetch</span><span class="p">(</span><span class="s1">&#39;/api/todo&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;getTodos data: &#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">todos</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">async</span> <span class="kd">function</span> <span class="nx">createTodo</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">useFetch</span><span class="p">(</span><span class="s1">&#39;/api/todo&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">method</span><span class="o">:</span> <span class="s1">&#39;POST&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">body</span><span class="o">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="nx">title</span><span class="o">:</span> <span class="nx">title</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span> <span class="nx">status</span><span class="o">:</span> <span class="s1">&#39;in progress&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;createTodo data: &#39;</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">todos</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">// execute getTodos on mounted
</span></span></span><span class="line"><span class="cl"><span class="nx">onBeforeMount</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Fetching todos..&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">getTodos</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">h5</span><span class="p">&gt;</span><span class="nx">Add</span> <span class="nx">Todo</span><span class="p">&lt;/</span><span class="nt">h5</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;field p-fluid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;title&#34;</span><span class="p">&gt;</span><span class="nx">Description</span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">InputText</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;title&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text&#34;</span> <span class="nt">v-model</span><span class="o">=</span><span class="s">&#34;title&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">Button</span> <span class="nt">@click</span><span class="s">=&#34;createTodo&#34;</span><span class="p">&gt;</span><span class="na">Add</span><span class="p">&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;card p-fluid&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">h5</span><span class="p">&gt;</span><span class="nx">My</span> <span class="nx">Todos</span><span class="p">&lt;/</span><span class="nt">h5</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;!todos &amp;&amp; todos.value.length &lt;= 0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">No</span> <span class="na">todos</span> <span class="na">yet.</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">DataView</span> <span class="nt">:value</span><span class="o">=</span><span class="s">&#34;todos.value&#34;</span> <span class="nt">v-else</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">template</span> <span class="err">#</span><span class="na">list</span><span class="o">=</span><span class="s">&#34;slotProps&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;col-12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex flex-column xl:flex-row xl:align-items-start p-4 gap-4&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-2xl font-bold text-900&#34;</span><span class="p">&gt;{{</span> <span class="nx">slotProps</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span> <span class="p">}}&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">DataView</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>This page will allow user to create new todo&rsquo;s and also query the API for todo&rsquo;s.</p>
<p><img loading="lazy" src="/2023/nuxt-primevue-todo-page.png" type="" alt="nuxt-primevue-todo-page"  /></p>
<p>Note that-</p>
<ol>
<li>We have used <code>onBeforeMount</code> to execute the <code>getTodos</code> function on page load. While <code>onMounted</code> is the more common way to do this, <code>onBeforeMount</code> gets triggered for page refreshes as well</li>
<li><code>DataView</code> and <code>DataTable</code> in PrimeVue makes it quite easy to display list data. I have used <code>DataView</code> here, but <code>DataTable</code> can be more beneficial for larger data sets</li>
<li>Typical apps need some validation on the forms - both on the page and on the server. I have not included that here for brevity</li>
</ol>
<p>And, that&rsquo;s a wrap for today, folks.</p>
<h1 id="conclusion">Conclusion</h1>
<ul>
<li>Nuxt can easily be a full-stack app</li>
<li>I cannot get myself to like Nuxt as the go-to full stack app
<ul>
<li>I am spoilt by the ease-of-use of a server framework - middleware, routes, controller/service included</li>
<li>My typical servers include functions that are not covered here and would need separate treatment anyway - e.g., batch processing / scheduled jobs</li>
<li>Server/client architecture looks cleaner to read / code</li>
<li>I perceive the typical server/client segregation to be easier to maintain without forcing a hard push for all concerns</li>
</ul>
</li>
<li>Also in other news-
<ul>
<li>I have come to admire <code>Drizzle</code> but cannot get over the intuitiveness of <code>knex</code>. Also, I have come to fear the sometimes drastic changes happening in smaller libraries across versions</li>
<li>PrimeVue will probably be my new library of choice (was Vuetify earlier)</li>
</ul>
</li>
</ul>
<p>I keep looking for &ldquo;one perfect way&rdquo; to get things done faster (I am more of a casual developer), but that is still in the future I guess.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Routing and Route Auth in Nuxt</title>
      <link>https://techformist.com/routing-auth-nuxt/</link>
      <pubDate>Wed, 05 Aug 2020 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/routing-auth-nuxt/</guid>
      <description>&lt;p&gt;Nuxt enables automatic routing. All you need to do is -&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create the Nuxt app&lt;/li&gt;
&lt;li&gt;Create Vue pages under &lt;code&gt;pages&lt;/code&gt; root directory&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All pages will automatically be routes in the app - no need for a distinct Vue router definition.&lt;/p&gt;
&lt;h2 id=&#34;automatic-routes-in-nuxt&#34;&gt;Automatic Routes in Nuxt&lt;/h2&gt;
&lt;p&gt;Consider the below pages in an example app &lt;a href=&#34;https://my-app.com&#34;&gt;https://my-app.com&lt;/a&gt;-&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;/pages/index.vue&lt;/code&gt; for your home page =&amp;gt; maps to =&amp;gt; &lt;a href=&#34;https://my-app.com/&#34;&gt;https://my-app.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;/pages/contact.vue&lt;/code&gt; for your contact page =&amp;gt; maps to =&amp;gt; (&lt;a href=&#34;https://my-app.com/contact&#34;&gt;https://my-app.com/contact&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;/pages/customers.vue&lt;/code&gt; for to display customers =&amp;gt; maps to =&amp;gt; &lt;a href=&#34;https://my-app.com/customers&#34;&gt;https://my-app.com/customers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The directory structure in &lt;code&gt;pages&lt;/code&gt;..&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Nuxt enables automatic routing. All you need to do is -</p>
<ol>
<li>Create the Nuxt app</li>
<li>Create Vue pages under <code>pages</code> root directory</li>
</ol>
<p>All pages will automatically be routes in the app - no need for a distinct Vue router definition.</p>
<h2 id="automatic-routes-in-nuxt">Automatic Routes in Nuxt</h2>
<p>Consider the below pages in an example app <a href="https://my-app.com">https://my-app.com</a>-</p>
<ul>
<li>Create <code>/pages/index.vue</code> for your home page =&gt; maps to =&gt; <a href="https://my-app.com/">https://my-app.com/</a></li>
<li>Create <code>/pages/contact.vue</code> for your contact page =&gt; maps to =&gt; (<a href="https://my-app.com/contact">https://my-app.com/contact</a></li>
<li>Create <code>/pages/customers.vue</code> for to display customers =&gt; maps to =&gt; <a href="https://my-app.com/customers">https://my-app.com/customers</a></li>
</ul>
<p>The directory structure in <code>pages</code>..</p>
<pre tabindex="0"><code>pages/
--| index.vue
--| contact.vue
--| customers.vue
</code></pre><p>.. is equivalent to the below router file -</p>
<pre tabindex="0"><code>router: {
  routes: [
    {
      path: &#39;/&#39;,
      component: &#39;pages/index.vue&#39;,
    },
    {
      path: &#39;/contact&#39;,
      component: &#39;pages/contact.vue&#39;,
    },
    {
      path: &#39;/customers&#39;,
      component: &#39;pages/customers.vue&#39;,
    },
  ]
}
</code></pre><p>You can also have dynamic routes -</p>
<pre tabindex="0"><code>pages/
--| _id.vue
</code></pre><p>.. which is equivalent to =&gt; <a href="https://my-app.com/a1iDThis">https://my-app.com/a1iDThis</a></p>
<p>.. or nested routes..</p>
<pre tabindex="0"><code>pages/
--| customers/
-----| _id.vue
-----| index.vue
--| contact.vue
--| index.vue
</code></pre><p>.. which will generate =&gt; -</p>
<ul>
<li><a href="https://my-app.com/customers/c123">https://my-app.com/customers/c123</a></li>
<li><a href="https://my-app.com/customers/">https://my-app.com/customers/</a></li>
<li><a href="https://my-app.com/contact/">https://my-app.com/contact/</a></li>
<li><a href="https://my-app.com/">https://my-app.com/</a></li>
</ul>
<h2 id="using-route-middleware">Using Route Middleware</h2>
<p>Use middleware to perform dark magic on routes. You can protect routes and make them accessible only for authenticated users for example.</p>
<p>Create <code>middleware</code> directory in Nuxt root folder, and create a new file called <code>auth.js</code> therein.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="p">({</span> <span class="nx">store</span><span class="p">,</span> <span class="nx">redirect</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">client</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">store</span><span class="p">.</span><span class="nx">getters</span><span class="p">[</span><span class="s2">&#34;isLoggedIn&#34;</span><span class="p">])</span> <span class="k">return</span> <span class="nx">redirect</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The above code is doing two things -</p>
<ol>
<li>check whether current execution context is <code>client</code> (we don&rsquo;t want this check to happen on server - our cookie/session variables exist in client browser )</li>
<li>if current user is not logged in (provided by <code>isLoggedIn</code> ), redirect to <code>/login</code> page</li>
</ol>
<p>Tell Nuxt about this middleware - edit <code>nuxt.config.js</code> -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ..
</span></span></span><span class="line"><span class="cl">  <span class="nx">middleware</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;auth&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ..
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>To use this middleware, just include the below code block in your page -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">middleware</span><span class="o">:</span> <span class="s2">&#34;auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Now, Nuxt will redirect user to <code>/login</code> page if not already authenticated (i.e., <code>isLoggedIn</code> is <code>false</code>).</p>
<h2 id="override-default-behaviour-bring-your-own-router">Override Default Behaviour: Bring your own router</h2>
<p>You can always assume more control in routing if you are still getting adjusted to the idea of &ldquo;automatic routing&rdquo;, or simply have too many things going on during routing.</p>
<p>Use <a href="https://github.com/nuxt-community/router-module">router-module</a> in your Nuxt project.</p>
<ol>
<li>
<p>Install <code>npm i --save @nuxtjs/router</code></p>
</li>
<li>
<p>Add to build modules in <code>nuxt.config.js</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">buildModules</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;@nuxtjs/router&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div></li>
<li>
<p>Create <code>router.js</code> in your Nuxt project and include the &ldquo;normal&rdquo; router code</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Vue</span> <span class="nx">from</span> <span class="s2">&#34;vue&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Router</span> <span class="nx">from</span> <span class="s2">&#34;vue-router&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Index</span> <span class="nx">from</span> <span class="s2">&#34;~/components/index&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Contact</span> <span class="nx">from</span> <span class="s2">&#34;~/components/contact&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">Vue</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">Router</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">createRouter</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="k">new</span> <span class="nx">Router</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mode</span><span class="o">:</span> <span class="s2">&#34;history&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">routes</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="nx">component</span><span class="o">:</span> <span class="nx">Index</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span> <span class="nx">path</span><span class="o">:</span> <span class="s2">&#34;/contact&#34;</span><span class="p">,</span> <span class="nx">component</span><span class="o">:</span> <span class="nx">Contact</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div></li>
</ol>
<h2 id="finis">Finis</h2>
<p>See <a href="https://nuxtjs.org/guide/routing/">more about Nuxt routing</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Structuring your Web App &amp; Nuxt Content Module</title>
      <link>https://techformist.com/structuring-web-app-nuxt/</link>
      <pubDate>Wed, 29 Jul 2020 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/structuring-web-app-nuxt/</guid>
      <description>&lt;p&gt;I have always struggled with new projects. Don&amp;rsquo;t get me wrong - we live in exciting times and all that. But, there is this thing called &amp;ldquo;conundrum of choice&amp;rdquo;. There are just too many ways to get things done and the &amp;ldquo;next shiny thing&amp;rdquo; syndrome rears its head each and every darn time.&lt;/p&gt;
&lt;p&gt;I can safely say that I have never really achieved a standard flow to get things started. Why is that important did I hear you ask?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have always struggled with new projects. Don&rsquo;t get me wrong - we live in exciting times and all that. But, there is this thing called &ldquo;conundrum of choice&rdquo;. There are just too many ways to get things done and the &ldquo;next shiny thing&rdquo; syndrome rears its head each and every darn time.</p>
<p>I can safely say that I have never really achieved a standard flow to get things started. Why is that important did I hear you ask?</p>
<ul>
<li>Starting a project is what I find more difficult than putting in the hours in the app, or completing the project</li>
<li>I explore available options far too deeply than what sane people do</li>
<li>I lose valuable time in &ldquo;starting things the right way&rdquo; rather than focusing on value-adding activities that deliver the goods</li>
</ul>
<p>So then, it is also safe to say I am forever searching for the one true framework/boiler plate for everything that a person needs to start a project. And that search, ladies and gentlemen, may have taken a big step towards conclusion.</p>
<p>Dramatic drumroll.. Nuxt.</p>
<p><img loading="lazy" src="/2020/nuxtjs-logo.jpg" type="" alt="nuxtjs_logo"  /></p>
<p>Before I go into why I am rick-rolling(?) Nuxt in all this, let us consider what goes in a web app for low-mid complexity web app.</p>
<h2 id="structure-of-the-modern-web-app">Structure of the Modern Web App</h2>
<p>I roll out products by myself. These are sometimes SaaS products, a few of them paid, and a lot of them created for small &amp; medium scale businesses that are not looking at rapid scaling by the end of the month.</p>
<p>My typical use case will consist of -</p>
<ol>
<li>Creating the app itself (insert favourite server-side and client-side framework here). This will consist of user auth incl. login/signup pages, app pages, and associated paraphernalia</li>
<li>Create a website that sings hymns about the app. Include a somewhat agreeable home page, pricing, help pages, T&amp;C pages etc.</li>
<li>Create a blog that can provide updates and do a dance or two for SEO</li>
</ol>
<p>While this is straight forward enough, I had days &amp; nights when I would have nightmares on what things go where, and how should I get stuff done quickly.</p>
<ul>
<li>SSR or Pre-render</li>
<li>Consistent styling across website and app</li>
<li>Easy to use, editor-friendly blogs</li>
<li>SEO friendly</li>
</ul>
<h2 id="meet-my-solutions">Meet My Solutions</h2>
<p>As I made pretty clear multiple times till now, I don&rsquo;t have a clear cookie-cutter solution. My go-to solution consisted of -</p>
<ol>
<li>Using a single page app (always an SPA)
<ul>
<li>Connect to backend through RESTful APIs / use web sockets / sessions</li>
<li>Use pre-render for pages with lot of content</li>
</ul>
</li>
<li>Use a static site generator for the larger site and blog</li>
<li>Go back and forth between components to get them all to work together</li>
</ol>
<p>Obviously, I could (or should) have steered clear of this mess by using server-rendered pages using templates (think Laravel, or numerous Node frameworks), I somehow did not quite take a firm step in that direction.</p>
<p>My technologies of choice to get things going had been -</p>
<ol>
<li><a href="https://vuejs.org/">Vue</a> - for SPA. <a href="https://nuxtjs.org/">Nuxt</a> for added SSR effect</li>
<li><a href="https://gohugo.io/">Hugo</a> - for static sites and blog. (OR) <a href="https://wordpress.org/">Wordpress</a> for sites &amp; blog if I want to feel like living on the edge</li>
</ol>
<p>I had gravitated towards using <a href="https://gridsome.org/">Gridsome</a> or <a href="https://saber.land">Saber</a> for sites/blog since then. They made it a lot easier to keep sites and the app consistent - for a while.</p>
<h2 id="my-new-favourite---nuxt">My New Favourite - Nuxt</h2>
<p>You may have seen Nuxt in my choice of technologies. So, what changed and why this this big intro that may overshadow the entire post?</p>
<p>Two words - <a href="https://content.nuxtjs.org/">Nuxt content</a>.</p>
<p>Nuxt has been my preferred tool when I need SSR, but I don&rsquo;t quite like that experience a whole lot. I take issue with -</p>
<ol>
<li>Increased hot-reload times during development. Yes, this matters for a person like me who makes far too many design mistakes</li>
<li>Added complexity of having to deal with server-side and client-side logic. Also a few libraries tend to become more ape-like with SSR (even with <code>&lt;client-only&gt;</code>)</li>
<li>Inconsistent, weird build behaviour - especially when I am using Vuetify/ Bootstrap-Vue. (Yes, I cannot reproduce those errors. Yes, I somehow manage to change config and make them go away)</li>
</ol>
<p>But, more recently I have taken a liking to the &ldquo;new life cycle of development&rdquo;.</p>
<ol>
<li>Nuxt for SPA</li>
<li>Nuxt for the entire site and blog</li>
</ol>
<p>Now I have -</p>
<ul>
<li>consistent styling</li>
<li>a common / consistent way of coding</li>
<li>less maintenance work</li>
</ul>
<p>What changed the game for me was the content module.</p>
<h2 id="why-content-module-is-awesome">Why Content Module is awesome?</h2>
<p>My first introduction to the content module was through <a href="https://nuxtjs.org/blog/creating-blog-with-nuxt-content/">this article on Nuxt blog</a> - published last month. Like all content on Internet, I bookmarked it and moved on with life.</p>
<p>It was only much later that I started a &ldquo;oh-so-cool&rdquo; project, and I looked out for anything new that could help me be a bit more productive. Boy, was I in for a surprise.</p>
<p>Nuxt content does one awesome thing - it sources the content during build and creates the pages that are SEO-friendly, SSR-friendly and can be delivered to the client at the blink of an eye.</p>
<ul>
<li>Create a content folder and put in your markdown files, JSON, CSV or XML files in there</li>
<li>Point Nuxt to content folder. Enable the module</li>
<li>Fetch your content in relevant pages</li>
<li>Sit back and start building</li>
</ul>
<p>In other words the content module bestows &ldquo;static site generation&rdquo; powers to your Nuxt app without actually making it a static site (nothing wrong with a static site per se).</p>
<p>So, now I could -</p>
<ol>
<li>Separate out content and presentation layers (greater reuse)</li>
<li>Use one framework for content heavy pages and my app</li>
<li>Choose SSR or SPA and do not make a fuss about overall site vs. app</li>
</ol>
<h2 id="getting-started">Getting Started</h2>
<p>Start your new nuxt project.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">npx create-nuxt-app todo
</span></span></code></pre></div><p>Install content module.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">npm install @nuxt/content
</span></span></code></pre></div><p>Add module to your <code>nuxt.config</code> file -</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">//
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;modules&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;@nuxt/content&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You are now ready to unleash your content. Create a <code>content</code> folder in root - you can add any subfolder or files to this folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl"><span class="k">md</span> content
</span></span><span class="line"><span class="cl"><span class="k">cd</span> content
</span></span><span class="line"><span class="cl"><span class="k">md</span> blog
</span></span><span class="line"><span class="cl"><span class="k">cd</span> blog
</span></span><span class="line"><span class="cl">touch hello.md
</span></span></code></pre></div><p>Write in your awesome post in <code>hello.md</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: Hello World
</span></span><span class="line"><span class="cl">description: Here&#39;s an obligatory &#39;hello world&#39; post
</span></span><span class="line"><span class="cl">createdAt: 2020-01-01
</span></span><span class="line"><span class="cl">slug: hello
</span></span><span class="line"><span class="cl">tags:
</span></span><span class="line"><span class="cl">  <span class="k">-</span> test
</span></span><span class="line"><span class="cl">  <span class="k">-</span> another
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Hello World
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">I just came here to say that.
</span></span></code></pre></div><p>Create a template that can render this markdown. Create a folder called <code>blog</code> under the root <code>pages</code> directory.</p>
<p>Create a new file <code>index.vue</code>. This file will be the layout to display our main blog content (e.g. our list of posts) - I am using Vuetify here, but you can use any style library.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;posts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">v-card</span>
</span></span><span class="line"><span class="cl">      <span class="na">color</span><span class="o">=</span><span class="s">&#34;grey lighten-3&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">flat</span>
</span></span><span class="line"><span class="cl">      <span class="na">width</span><span class="o">=</span><span class="s">&#34;100%&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">height</span><span class="o">=</span><span class="s">&#34;150px&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-center pt-6 pb-6 mb-2&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-h5 grey--text&#34;</span><span class="p">&gt;</span>Blog<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grey--text caption mt-8&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">nuxt-link</span> <span class="na">to</span><span class="o">=</span><span class="s">&#34;/&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grey--text&#34;</span><span class="p">&gt;</span>Home<span class="p">&lt;/</span><span class="nt">nuxt-link</span><span class="p">&gt;</span> / Blog
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">v-card</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">v-container</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">v-row</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">v-col</span> <span class="na">cols</span><span class="o">=</span><span class="s">&#34;12&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">v-for</span><span class="o">=</span><span class="s">&#34;post in posts&#34;</span> <span class="na">:key</span><span class="o">=</span><span class="s">&#34;post.dir&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-h4 font-weight-bold mb-6&#34;</span><span class="p">&gt;</span>{{ post.title }}<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;grey--text caption mb-4&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">&lt;</span><span class="nt">span</span> <span class="na">v-if</span><span class="o">=</span><span class="s">&#34;post.createdAt&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">v-icon</span> <span class="na">small</span> <span class="na">color</span><span class="o">=</span><span class="s">&#34;grey&#34;</span><span class="p">&gt;</span>mdi-calendar<span class="p">&lt;/</span><span class="nt">v-icon</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                {{ post.createdAt.substr(0, 10) }}
</span></span><span class="line"><span class="cl">              <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              | Tags:
</span></span><span class="line"><span class="cl">              <span class="p">&lt;</span><span class="nt">span</span> <span class="na">v-for</span><span class="o">=</span><span class="s">&#34;(tag, i) in post.tags&#34;</span> <span class="na">:key</span><span class="o">=</span><span class="s">&#34;i&#34;</span> <span class="na">justify</span><span class="o">=</span><span class="s">&#34;around&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">v-chip</span> <span class="na">dense</span> <span class="na">small</span> <span class="na">color</span><span class="o">=</span><span class="s">&#34;grey lighten-3&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mr-1&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                  {{ tag }}
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">v-chip</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>{{ post.description }}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">nuxt-link</span> <span class="na">:to</span><span class="o">=</span><span class="s">&#34;`/blog/${post.slug}`&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;button&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">&gt;</span>Read more<span class="p">&lt;/</span><span class="nt">nuxt-link</span>
</span></span><span class="line"><span class="cl">            <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">v-col</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">v-row</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">v-container</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">async</span> <span class="nx">asyncData</span><span class="p">({</span> <span class="nx">params</span><span class="p">,</span> <span class="nx">error</span><span class="p">,</span> <span class="nx">$content</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">$content</span><span class="p">(</span><span class="s1">&#39;blog&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">deep</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}).</span><span class="nx">fetch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Your discerning mind would have noted a few things here -</p>
<ol>
<li>We used <code>asyncData</code> in the page to retrieve content. This happens during build time. You are free to work your SEO magic here</li>
<li>You utilise the content read during <code>asyncData</code> like any other Vue variable</li>
<li>You did nothing additional other than a plan-ol&rsquo; Nuxt project - which is a good thing</li>
</ol>
<p>After all that hard work, your Blog is ready to roll.</p>
<p><img loading="lazy" src="/2020/nuxt-content-module-create-blog.jpg" type="" alt="nuxt-content-module-create-blog"  /></p>
<p>We did all this without leaving the framework that is also responsible for our app. So, the real question you should be asking now is &ldquo;have I reached app starter nirvana?&rdquo;.</p>
<h2 id="finis">Finis</h2>
<p>What do you think? What will you do for your next big app!?</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
