DEV Community

Cover image for From Tailwind CLI to Vite: A Developer’s Journey to Better Performance (2025 Edition)
gerry leo nugroho
gerry leo nugroho

Posted on

From Tailwind CLI to Vite: A Developer’s Journey to Better Performance (2025 Edition)

I’m always on the lookout for tools that can make my workflow faster, more efficient, and enjoyable. Recently, I decided to integrate Vite into my existing TailwindCSS CLI project, all-things-digital. My goal was to modernize my build process while keeping the simplicity of my current setup intact. In this blog post, I’ll walk you through the entire process—from creating a backup branch, integrating Vite into my current codebase and modularizing my code to take advantage of what Vite has to offer. By the end of this journey, you’ll have a clear understanding of how to upgrade your own project with Vite while maintaining modularity and performance.

1. Create a New Branch for Backup

Before diving into any major changes, it’s essential to create a backup branch. This ensures that your current setup remains untouched in case something goes wrong during the upgrade process. Here’s how I did it:

Step 1.1: Open Your Terminal

I opened my terminal and navigated to the root directory of my project. If you’re using a Mac or Linux, you’ll use Bash, while Windows users can use the command prompt .

Step 1.2: Create the Backup Branch

To create a new branch named latest-vite-integration, I ran the following command:

git checkout -b latest-vite-integration
Enter fullscreen mode Exit fullscreen mode

This command creates a new branch and switches to it in one step . It’s a quick and efficient way to ensure I have a safe starting point.

Step 1.3: Push the Branch to GitHub

After creating the branch locally, I pushed it to GitHub to back it up remotely:

git push origin latest-vite-integration
Enter fullscreen mode Exit fullscreen mode

This step ensures that the branch is stored securely in the cloud, making it accessible from anywhere .

Why This Step Matters

Creating a backup branch is a best practice I’ve learned over time. It gives me peace of mind knowing that I can always revert to my original setup if needed. Plus, it allows me to experiment freely without worrying about breaking anything .


2. Vite Best Practices in 2025

Before integrating Vite into my project, I wanted to ensure I understood its best practices and how it could improve my workflow. This step was crucial for making informed decisions and avoiding common pitfalls. Here’s what I learned:

What is Vite?

Vite is a modern build tool that leverages native ES modules (ESM) to provide a lightning-fast development experience . Unlike traditional bundlers like Webpack, Vite serves files on demand during development, eliminating the need for a full rebuild every time you make a change. This makes it particularly well-suited for static HTML/CSS/JS projects like mine.

Key Features of Vite

  1. Hot Module Replacement (HMR):

    One of Vite’s standout features is its HMR capability. When you edit a file, only the changed module is updated in the browser, without reloading the entire page. This makes development faster and more enjoyable .

  2. Optimized Production Builds:

    While Vite uses native ESM during development, it bundles your code into optimized assets for production. This ensures your site remains performant when deployed .

  3. Seamless Integration with Modern Tools:

    Vite works well with frameworks like React and libraries like TailwindCSS. It also supports TypeScript out of the box, making it future-proof for potential upgrades .

  4. Minimal Configuration:

    Vite is designed to require minimal setup. For most projects, the default configuration is sufficient, but it’s also highly customizable if needed .

How Vite Differs from My Current Setup

Currently, I’m using the Tailwind CLI with NPM scripts to process my CSS. While this setup works, it lacks the speed and modularity that Vite offers. Here’s a comparison:

Feature Current Setup (Tailwind CLI + NPM Scripts) Vite
Development Server No built-in server Built-in, fast, and feature-rich
Hot Module Replacement (HMR) Manual refresh required Instant updates without full reload
Production Optimization Manual minification Automatic optimization and bundling
Modularity Limited Encourages modular components via ES modules

Folder Structure Recommendations

Vite encourages a modular folder structure to keep your project organized. Based on my research, here’s how I plan to restructure my project:

src/
├── components/
│   ├── header.html
│   ├── footer.html
│   └── content.html
├── layout/
│   └── base.html
├── main.css
└── index.html
Enter fullscreen mode Exit fullscreen mode

This structure allows me to break my code into smaller, reusable components. For example, instead of writing all my HTML in a single file, I can split it into logical pieces like header, footer, and content.

Resources I Found Helpful

  • The Vite documentation provided clear guidance on setting up and configuring the tool .
  • Articles like “Why Vite is the best?” highlighted its advanced features, such as seamless form handling and built-in state management .
  • Comparisons between Vite and traditional build tools like Webpack helped me understand its advantages in terms of speed and ease of use .

Why This Step Matters

Researching Vite’s best practices gave me confidence in my decision to adopt it. By understanding its strengths and how it differs from my current setup, I was able to plan a smooth transition. Plus, learning about its modular architecture inspired me to rethink how I organize my code.


3. Install Vite and Set Up the Development Environment

With a solid understanding of Vite’s best practices, I was ready to install it and configure my development environment. This step involved setting up Vite, configuring it to work with my existing TailwindCSS setup, and ensuring everything worked seamlessly. Here’s how I did it:

Step 3.1: Install Vite & Autoprefixer

To integrate Vite into my project, I started by installing it as a development dependency. In my terminal, I ran the following command:

npm install vite --save-dev
Enter fullscreen mode Exit fullscreen mode

This command added Vite to my devDependencies in package.json, making it available for use during development .

Though my current project setup with comes with a Tailwind CSS CLI already included, but there is one more dependencies that Vite may need additionally, the autoprefixer library.

npm install -D autoprefixer --save-dev
Enter fullscreen mode Exit fullscreen mode

By invoking the previous command in your terminal, it would add "autoprefixer": "^10.4.21", to my package.json at the same time.

Step 3.2: Add a Vite Configuration File

Next, I created a vite.config.mjs file in the root directory of my project. This file allows me to customize Vite’s behavior while keeping things simple. Here’s the configuration I used:

// Import required modules for Vite configuration
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/postcss';
import autoprefixer from 'autoprefixer';

/**
 * Vite configuration for the project.
 *
 * This config sets up:
 * - Project root directory
 * - Build output settings
 * - PostCSS plugins (Tailwind CSS and Autoprefixer)
 */
export default defineConfig({
  // Set the project root directory to './src'
  root: './src',

  // Build configuration
  build: {
    // Output directory for built files (relative to project root)
    outDir: '../dist',
    // Clear the output directory before each build
    emptyOutDir: true,
  },

  // CSS processing configuration
  css: {
    // PostCSS configuration
    postcss: {
      plugins: [
        // Add Tailwind CSS as a PostCSS plugin
        tailwindcss(),
        // Add Autoprefixer for vendor prefixing
        autoprefixer()
      ]
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Why This Configuration?

  • The root option specifies that Vite should look for my source files in the src folder.
  • The build.outDir option ensures that production builds are placed in the dist folder, matching my current workflow.
  • The css.postcss option integrates my existing PostCSS setup, ensuring TailwindCSS continues to work without issues .
"scripts": {
        "dev": "vite", // Start the Vite development server
        "build": "vite build", // Build for production
        "preview": "vite preview", // Preview the production build locally
        "watch": "tailwindcss -i ./src/main.css -o ./dist/style.css --watch",
        "compress": "npx tailwindcss -i ./src/main.css -o ./dist/style.css --minify",
        "start": "live-server ./dist"
    }
Enter fullscreen mode Exit fullscreen mode

Step 3.3: Update NPM Scripts

To make Vite part of my workflow, I updated the scripts section in my package.json. Here’s what I changed:

What Do These Scripts Do?

  • npm run dev: Starts the Vite development server, enabling hot module replacement (HMR) for a faster development experience.
  • npm run build: Creates an optimized production build in the dist folder.
  • npm run preview: Lets me preview the production build locally before deploying it.
  • npm run watch: Keeps my existing TailwindCSS workflow intact by watching for changes and rebuilding styles as needed .

4. Update Folder Structure for Modularity Using vite-plugin-ejs

To achieve modularity in my project, I decided to use the vite-plugin-ejs plugin . This plugin allows me to break my HTML into smaller, reusable components and assemble them dynamically using EJS templates. Below, I’ll walk you through the entire process—from installing the plugin to restructuring the folder and file structure, and finally modularizing the code.

Step 4.1: Install vite-plugin-ejs

The first step was to install the vite-plugin-ejs plugin as a development dependency. In my terminal, I ran the following command:

npm install vite-plugin-ejs --save-dev
Enter fullscreen mode Exit fullscreen mode

This added the plugin to my devDependencies in package.json, making it available for use in my Vite configuration .

Step 4.2: Update vite.config.js

Next, I updated my vite.config.js file to include the vite-plugin-ejs plugin. Here’s the updated configuration:

// Import required modules for Vite configuration
import { defineConfig } from 'vite'
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'
import imagemin from 'vite-plugin-imagemin'
import htmlMinifier from 'vite-plugin-html-minifier'
import { ViteEjsPlugin } from 'vite-plugin-ejs'
import { resolve } from 'path'

/**
 * Vite configuration for the project.
 *
 * This config sets up:
 * - Project root directory
 * - Build output settings
 * - PostCSS plugins (Tailwind CSS and Autoprefixer)
 * - Images, HTML, CSS, and JavaScript compressions using Vite plugins
 * - EJS templating for HTML modularity
 */
export default defineConfig({
  // Set the project root directory to './src'
  root: './src',

  // Build configuration
  build: {
    // Output directory for built files (relative to project root)
    outDir: '../dist',
    // Clear the output directory before each build
    emptyOutDir: true,
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'src/index.html'),
        about: resolve(__dirname, 'src/about.html'),
        contact: resolve(__dirname, 'src/contact.html'),
        blog: resolve(__dirname, 'src/blog.html'),
        category: resolve(__dirname, 'src/category.html'),
      },
    },
  },

  // CSS processing configuration
  css: {
    // PostCSS configuration
    postcss: {
      plugins: [tailwindcss, autoprefixer],
    },
  },

  // Plugins configuration
  plugins: [
    // Image minification
    imagemin({
      pngquant: {
        quality: [0.7, 0.9],
        speed: 4,
      },
    }),
    // HTML minification
    htmlMinifier({
      minify: true,
      collapseWhitespace: true,
      keepClosingSlash: true,
      removeComments: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      useShortDoctype: true,
    }),
    // EJS templating for HTML
    ViteEjsPlugin(),
  ],

  server: {
    watch: {
      usePolling: true,
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Why Use vite-plugin-ejs?

This plugin enables me to use EJS templates in my HTML files, allowing me to modularize my code into reusable components like header.html, navigation.html, and more .

Step 4.3: Restructure the Folder and File Structure

To modularize my code, I reorganized my project folder structure. Here’s the updated structure:

src/
├── components/
│   ├── Footer.html
│   ├── Header.html
│   ├── Navigation.html
│   ├── nav-desktop.html
│   └── nav-mobile.html
├── layouts/
│   └── base.html
├── content/
│   └── content-about.html
│   └── content-blog-grid.html
│   └── content-blog.html
│   └── content-category.html
│   └── content-home.html
├── main.css
└── index.html
Enter fullscreen mode Exit fullscreen mode

What Changed?

  • I moved all reusable components (e.g., header, footer) into the components folder.
  • I created a layouts folder to house the base.html layout file, which serves as the template for assembling components.
  • I renamed index.html to index.html to leverage EJS templating .

Step 4.4: Modularize the Code

Base Layout (base.html)

The base.html file acts as the skeleton of my HTML structure. It includes placeholders for injecting components like header, navigation, content, and footer. Here’s the full code:

<!DOCTYPE html>
<html lang="en">
  <%- include('components/header.html') %>
  <body class="antialiased bg-white dark:bg-main scroll-smooth">
    <%- include('components/navigation.html') %>
    <%- include(typeof content !== 'undefined' ? content : 'content/content-home.html') %>
    <%- include('components/footer.html') %>
    <script type="module" src="./js/app.js"></script>
    <script type="module" src="./js/script.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

How Does It Work?

The <%- include(...) %> syntax dynamically injects the specified EJS component into the layout. This approach keeps the code modular and reusable .

Header Component (header.html)

<head>
  <link rel="manifest" href="./manifest.json">
  <meta name="msapplication-TileColor" content="#ffffff">
  <meta name="msapplication-TileImage" content="./ms-icon-144x144.png">
  <meta name="theme-color" content="#ffffff">
  <link rel="shortcut icon" href="./favicon.ico" type="image/x-icon">
  <link rel="stylesheet" href="./main.css">
  <title></title>
</head>
Enter fullscreen mode Exit fullscreen mode

The header.html file contains the HTML for the site’s header. Here’s the full code:

Navigation Component (navigation.html)

The navigation.html file contains the site’s navigation menu. Here’s the full code:

<!-- start the logo, desktop-nav, darkmode button, contact button, nav-icon & mobile-nav  -->
<header id="wrapperNavBar"
  class="sticky top-0 z-10 bg-white p-[25px] drop-shadow-xl blurGrad backdrop-blur-lg dark:bg-main dark:drop-shadow-2xl dark:border-b dark:border-slate-900/20">
  <div id="NavBar" class="flex justify-between max-w-4xl mx-auto">
    <!-- start the logo -->
    <div id="logo" class="z-40 drop-shadow-md">
      <a href="index.html">
        <img src="./img/icons/icon-dgpondcom.png" alt="Logo" id="DGPond" class="w-auto h-auto max-w-full">
      </a>
    </div>
    <%- include('components/nav-desktop.html') %>
    <%- include('components/nav-mobile.html') %>
  </div>
</header>
Enter fullscreen mode Exit fullscreen mode

Footer Component (footer.html)

The footer.ejs file contains the site’s footer. Here’s the full code:

<!-- src/components/footer.html -->
<footer class="bg-main text-white py-6">
  <div class="container mx-auto text-center">
    <p>&copy; 2025 All Things Digital. All rights reserved.</p>
  </div>
</footer>
Enter fullscreen mode Exit fullscreen mode

Main Entry Point (index.html)

Finally, the index.ejs file serves as the entry point for the application. It simply includes the base.ejs layout:

<!-- src/index.ejs -->
<%- include('layouts/base.html') %>
Enter fullscreen mode Exit fullscreen mode

Step 4.5: Test the Setup

After modularizing the code, I tested the setup by running the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Vite launched the server at http://localhost:5173, and I verified that:

  • The Tailwind styles were applied correctly.
  • All components rendered as expected.
  • Hot module replacement (HMR) worked when editing files .

What Have We Achieved So Far?

Modularizing my code using vite-plugin-ejs made my project easier to maintain and scale. By breaking the HTML into reusable components, I reduced duplication and improved readability. Plus, the use of EJS templates ensured that changes to shared components (like the header or footer) were reflected across all pages automatically .


5. Update NPM Scripts

With my project now modularized and Vite fully integrated, it was time to update my package.json scripts to align with the new workflow. This step ensures that I can efficiently run, build, and preview my project using Vite’s powerful features. Here’s how I approached it:

Step 5.1: Analyze My Current Scripts

Before making changes, I reviewed my existing scripts section in package.json. Here’s what I had:

"scripts": {
  "dev": "tailwindcss -i ./src/main.css -o ./dist/style.css",
  "build": "tailwindcss -i ./src/main.css -o ./dist/style.css",
  "watch": "tailwindcss -i ./src/main.css -o ./dist/style.css --watch",
  "compress": "npx tailwindcss -o ./dist/style.css --minify",
  "start": "live-server ./dist"
}
Enter fullscreen mode Exit fullscreen mode

While these scripts worked fine for my TailwindCSS setup, they didn’t leverage Vite’s capabilities. For example, the live-server command was unnecessary since Vite includes a built-in development server .

Step 5.2: Update Scripts for Vite

I updated my scripts section to incorporate Vite’s commands while keeping some of my existing workflows intact. Here’s the revised version:

"scripts": {
  "dev": "vite", // Start the Vite development server with hot module replacement (HMR)
  "build": "vite build", // Build the project for production
  "preview": "vite preview", // Preview the production build locally
  "tailwind:watch": "tailwindcss -i ./src/main.css -o ./dist/style.css --watch", // Watch and rebuild Tailwind styles during development
  "start": "npm run dev" // Alias for starting the development server
}
Enter fullscreen mode Exit fullscreen mode

What Do These Scripts Do?

  • npm run dev: Starts the Vite development server, enabling HMR for a faster and more interactive development experience .
  • npm run build: Creates an optimized production build in the dist folder, ready for deployment.
  • npm run preview: Allows me to preview the production build locally before deploying it. This is particularly useful for testing performance and functionality .
  • npm run tailwind:watch: Keeps my existing TailwindCSS workflow intact by watching for changes and rebuilding styles as needed.
  • npm run start: Acts as a convenient alias for starting the development server.

Step 5.3: Test the Updated Scripts

To ensure everything worked as expected, I tested each script:

  1. Start the Development Server

    I ran the following command:

    npm run dev
    

    Vite launched the development server at http://localhost:5173, and my modular components rendered perfectly. Hot module replacement (HMR) worked seamlessly, updating the browser instantly when I made changes .

  2. Build for Production

    Next, I built the project for production:

    npm run build
    

    Vite generated an optimized build in the dist folder. The output included minified CSS and JavaScript files, ensuring high performance .

  3. Preview the Production Build

    To verify the production build, I ran:

    npm run preview
    

    Vite served the production build locally, allowing me to test its functionality and performance before deploying it.

  4. Watch Tailwind Styles

    Finally, I tested the tailwind:watch script:

    npm run tailwind:watch
    

    This ensured that my Tailwind styles were rebuilt automatically whenever I made changes to my CSS or HTML files .

Why This Step Matters

Updating my NPM scripts streamlined my workflow and aligned it with Vite’s best practices. By replacing live-server with Vite’s built-in development server, I eliminated redundancy and improved performance. Additionally, the preview script gave me confidence in my production builds before deploying them. For further reading on Vite’s capabilities, I recommend checking out the official Vite documentation .


6. Adjust TailwindCSS Setup

With Vite and vite-plugin-ejs now fully integrated into my project, it was time to adjust my TailwindCSS setup to ensure it worked seamlessly with the new modular structure. This step involved updating the tailwind.config.js file, ensuring proper content scanning, and optimizing the workflow for both development and production environments.

Step 6.1: Update tailwind.config.mjs

The first thing I did was update the content property in my tailwind.config.js file. Since I had modularized my HTML into .ejs files, I needed to ensure Tailwind scanned these files for class names .

Here’s the updated configuration:

const colors = require("tailwindcss/colors");

/** @type {import('tailwindcss').Config} */
module.exports = {
  // Scan for Tailwind classes in .ejs files
  content: [
    "./src/**/*.{html,ejs,js,ts}", // Include all HTML, EJS, and JS files in the src folder
  ],
  darkMode: "class",
  theme: {
    screens: {
      sm: "480px",
      md: "768px",
      lg: "976px",
      xl: "1440px",
    },
    extend: {
      animation: {
        blob: "blob 7s infinite",
      },
      colors: {
        savoryWhite: "hsl(0, 36%, 95%)",
        mutedWhite: "hsl(224, 16%, 54%)",
        whiteHover: "hsl(0, 36%, 95%)",
        pinkHeading: "hsl(279, 48%, 54%)",
        pinkSubHeading: "hsl(293, 43%, 55%)",
        blackSubHeading: "hsl(221, 39%, 11%)",
        deepBlueSubHeading: "hsl(229, 38%, 40%)",
        fuchsia: colors.fuchsia,
      },
      keyframes: {
        blob: {
          "0%": {
            transform: "translate(0px, 0px) scale(1)",
          },
          "33%": {
            transform: "translate(30px, -50px) scale(1.1)",
          },
          "66%": {
            transform: "translate(-20px, 20px) scale(0.9)",
          },
          "100%": {
            transform: "translate(0px, 0px) scale(1)",
          },
        },
      },
      backgroundColor: {
        main: "hsl(215, 45%, 14%)",
      },
    },
    fontFamily: {
      navigation: ["Poppins", "sans-serif"],
    },
  },
  plugins: [
    require("@tailwindcss/typography"),
    require("@tailwindcss/forms"),
    require("@tailwindcss/line-clamp"),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Why This Change?

By including .html & .ejs files in the content array, I ensured that Tailwind scanned my modular components for class names. This prevents unused styles from being purged during production builds .

Step 6.2: Optimize TailwindCSS Workflow

To make my TailwindCSS workflow more efficient, I kept the tailwind:watch script in my package.json. This script watches for changes in my CSS and rebuilds styles automatically during development:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview",
  "tailwind:watch": "tailwindcss -i ./src/main.css -o ./dist/style.css --watch", // Watch and rebuild Tailwind styles
  "start": "npm run dev"
}
Enter fullscreen mode Exit fullscreen mode

This ensures that my styles are always up-to-date, even when working with modular both .html & .ejs files.

Step 6.3: Test the TailwindCSS Integration

To verify that TailwindCSS was working correctly, I performed the following tests:

  1. Development Environment

    I ran the tailwind:watch script alongside the Vite development server:

    npm run tailwind:watch & npm run dev
    

    This allowed me to see real-time updates to my styles as I edited my .html components .

  2. Production Build

    Next, I built the project for production:

    npm run build
    

    Vite generated an optimized build in the dist folder, including minified CSS and JavaScript files. I verified that all Tailwind classes were included and applied correctly .

  3. Preview the Production Build

    Finally, I previewed the production build locally:

    npm run preview
    

    The site rendered perfectly, with all styles intact and no missing classes.

Why This Step Matters

Adjusting my TailwindCSS setup ensured that it worked seamlessly with my new modular structure. By updating the content property in tailwind.config.js, I avoided issues with purging unused styles during production builds. Additionally, the tailwind:watch script streamlined my development workflow, making it easier to iterate on designs .

For further reading on integrating TailwindCSS with Vite, I recommend checking out this GitHub repository .


7. Test the Development Workflow

With my project now fully integrated with Vite, vite-plugin-ejs, and TailwindCSS, it was time to test the development workflow. This step ensured that everything worked as expected—both in development and production environments. Here’s how I approached it:

Step 7.1: Start the Development Server

I began by starting the Vite development server using the dev script:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Vite launched the server at http://localhost:5173, and my modular components rendered perfectly . The hot module replacement (HMR) feature worked seamlessly, updating the browser instantly whenever I made changes to my .html files or CSS.

What Did I Test?

  • HTML Components: I verified that all modular components (header.html, navigation.html, content.html, and footer.html) were rendered correctly within the base.html layout.
  • Tailwind Styles: I checked that all Tailwind classes were applied as expected, ensuring no styles were missing or broken.
  • Dynamic Updates: I edited one of my .html files (e.g., header.html) and confirmed that the changes appeared immediately in the browser without requiring a full reload .

Step 7.2: Test TailwindCSS Watch Mode

To ensure my styles were always up-to-date during development, I ran the tailwind:watch script alongside the Vite server:

npm run tailwind:watch & npm run dev
Enter fullscreen mode Exit fullscreen mode

This combination allowed me to see real-time updates to my styles as I edited my .html components or main.css file . For example, when I added a new Tailwind class like text-pinkHeading to the header.html file, the change was reflected instantly in the browser.

Step 7.3: Build for Production

Next, I tested the production build process by running:

npm run build
Enter fullscreen mode Exit fullscreen mode

Vite generated an optimized build in the dist folder, including minified CSS and JavaScript files. To verify the output, I inspected the dist folder and confirmed that:

  • All .html components were compiled into static HTML files.
  • TailwindCSS purged unused styles, resulting in a smaller CSS file size .
  • The production build included all necessary assets (e.g., images, fonts).

Here’s an example of the optimized index.html file generated in the dist folder:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>All Things Digital</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body>
  <!-- Header Component -->
  <header class="bg-savoryWhite text-blackSubHeading py-4">
    <div class="container mx-auto flex justify-between items-center">
      <h1 class="text-pinkHeading font-bold text-2xl">All Things Digital</h1>
      <button id="menu-toggle" class="hamburger">
        <span class="hamburger-top"></span>
        <span class="hamburger-middle"></span>
        <span class="hamburger-bottom"></span>
      </button>
    </div>
  </header>

  <!-- Navigation Component -->
  <nav class="hidden md:flex space-x-6 text-mutedWhite">
    <a href="/" class="hover:text-whiteHover">Home</a>
    <a href="/about.html" class="hover:text-whiteHover">About</a>
    <a href="/blog.html" class="hover:text-whiteHover">Blog</a>
    <a href="/contact.html" class="hover:text-whiteHover">Contact</a>
  </nav>

  <!-- Content Component -->
  <main>
    <section class="py-8">
      <div class="container mx-auto">
        <h2 class="text-pinkSubHeading text-3xl font-bold mb-4">Welcome to All Things Digital</h2>
        <p class="text-mutedWhite">
          Explore the latest trends, tips, and insights in the digital world.
        </p>
      </div>
    </section>
  </main>

  <!-- Footer Component -->
  <footer class="bg-main text-white py-6">
    <div class="container mx-auto text-center">
      <p>&copy; 2025 All Things Digital. All rights reserved.</p>
    </div>
  </footer>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Why Is This Important?

The production build ensures that my site is optimized for performance, with minimal file sizes and no unnecessary code. This is critical for delivering a fast and responsive user experience .


Step 7.4: Preview the Production Build

To test the production build locally, I ran:

npm run preview
Enter fullscreen mode Exit fullscreen mode

Vite served the production build at http://localhost:4173, allowing me to verify its functionality and performance. I performed the following checks:

  • Rendering: Ensured all components and styles rendered correctly.
  • Performance: Verified that the site loaded quickly, thanks to Vite’s optimized build process.
  • Responsiveness: Tested the site on different screen sizes to confirm that Tailwind’s responsive utilities worked as expected .

Step 7.5: Document Observations

Throughout the testing process, I documented my observations and added comments to relevant files. For example, here’s a comment I added to my vite.config.js file:

// Import required modules for Vite configuration
import { defineConfig } from 'vite'
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'
import imagemin from 'vite-plugin-imagemin'
import htmlMinifier from 'vite-plugin-html-minifier'
import { ViteEjsPlugin } from 'vite-plugin-ejs'
import { resolve } from 'path'

/**
 * Vite configuration for the project.
 *
 * This config sets up:
 * - Project root directory
 * - Build output settings
 * - PostCSS plugins (Tailwind CSS and Autoprefixer)
 * - Images, HTML, CSS, and JavaScript compressions using Vite plugins
 * - EJS templating for HTML modularity
 */
export default defineConfig({
  // Set the project root directory to './src'
  root: './src',

  // Build configuration
  build: {
    // Output directory for built files (relative to project root)
    outDir: '../dist',
    // Clear the output directory before each build
    emptyOutDir: true,
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'src/index.html'),
        about: resolve(__dirname, 'src/about.html'),
        contact: resolve(__dirname, 'src/contact.html'),
        blog: resolve(__dirname, 'src/blog.html'),
        category: resolve(__dirname, 'src/category.html'),
      },
    },
  },

  // CSS processing configuration
  css: {
    // PostCSS configuration
    postcss: {
      plugins: [tailwindcss, autoprefixer],
    },
  },

  // Plugins configuration
  plugins: [
    // Image minification
    imagemin({
      pngquant: {
        quality: [0.7, 0.9],
        speed: 4,
      },
    }),
    // HTML minification
    htmlMinifier({
      minify: true,
      collapseWhitespace: true,
      keepClosingSlash: true,
      removeComments: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      useShortDoctype: true,
    }),
    // EJS templating for HTML
    ViteEjsPlugin(),
  ],

  server: {
    watch: {
      usePolling: true,
    },
  },
})

Enter fullscreen mode Exit fullscreen mode

Why Add Comments?

Comments make the code easier to understand for future me—or anyone else who might work on the project. They also serve as a reminder of why certain decisions were made .

Why This Step Matters

Testing the development workflow ensured that my project was ready for both development and production environments. By verifying that Vite, vite-plugin-ejs, and TailwindCSS worked seamlessly together, I gained confidence in the stability and performance of my site . For further reading on conducting effective tests, I recommend this step-by-step guide for knowledge checks .


8. Build for Production

With my development workflow thoroughly tested, it was time to focus on building the project for production. This step ensures that my site is optimized for performance, with minimal file sizes and no unnecessary code. Here’s how I approached it:


Step 8.1: Run the Production Build Command

I started by running the build script, which generates an optimized production build in the dist folder:

npm run build
Enter fullscreen mode Exit fullscreen mode

Vite processed my modular .html & .ejs components, TailwindCSS styles, and JavaScript files, bundling them into a clean and efficient structure . The output included:

  • Minified CSS and JavaScript files.
  • Static HTML files compiled from my .html & .ejs templates.
  • All necessary assets (e.g., images, fonts) copied to the dist folder.

Here’s an example of the optimized index.html file generated in the dist folder:

<!-- dist/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>All Things Digital</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body>
  <!-- Header Component -->
  <header class="bg-savoryWhite text-blackSubHeading py-4">
    <div class="container mx-auto flex justify-between items-center">
      <h1 class="text-pinkHeading font-bold text-2xl">All Things Digital</h1>
      <button id="menu-toggle" class="hamburger">
        <span class="hamburger-top"></span>
        <span class="hamburger-middle"></span>
        <span class="hamburger-bottom"></span>
      </button>
    </div>
  </header>

  <!-- Navigation Component -->
  <nav class="hidden md:flex space-x-6 text-mutedWhite">
    <a href="/" class="hover:text-whiteHover">Home</a>
    <a href="/about.html" class="hover:text-whiteHover">About</a>
    <a href="/blog.html" class="hover:text-whiteHover">Blog</a>
    <a href="/contact.html" class="hover:text-whiteHover">Contact</a>
  </nav>

  <!-- Content Component -->
  <main>
    <section class="py-8">
      <div class="container mx-auto">
        <h2 class="text-pinkSubHeading text-3xl font-bold mb-4">Welcome to All Things Digital</h2>
        <p class="text-mutedWhite">
          Explore the latest trends, tips, and insights in the digital world.
        </p>
      </div>
    </section>
  </main>

  <!-- Footer Component -->
  <footer class="bg-main text-white py-6">
    <div class="container mx-auto text-center">
      <p>&copy; 2025 All Things Digital. All rights reserved.</p>
    </div>
  </footer>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

Why Is This Important?

The production build ensures that my site is ready for deployment, with all assets optimized for speed and efficiency. This is critical for delivering a fast and responsive user experience .

Step 8.2: Inspect the Output Folder

After the build process completed, I inspected the dist folder to verify its contents:

  • CSS: The style.css file was minified, ensuring faster load times.
  • HTML: My modular .ejs components were compiled into static HTML files, making them easy to serve.
  • Assets: Images, fonts, and other assets were copied to the dist folder, ensuring they were available for use.

Here’s a snapshot of the dist folder structure:

dist/
├── assets/
├── index.html
├── about.html
├── blog.html
├── style.css
└── script.js
Enter fullscreen mode Exit fullscreen mode

What Did I Learn?

Inspecting the dist folder gave me confidence that everything was in place for deployment. Vite’s build process handled all optimizations automatically, saving me time and effort .

Step 8.3: Preview the Production Build

To test the production build locally, I ran the preview script:

npm run preview
Enter fullscreen mode Exit fullscreen mode

Vite served the production build at http://localhost:4173, allowing me to verify its functionality and performance . I performed the following checks:

  • Rendering: Ensured all components and styles rendered correctly.
  • Performance: Verified that the site loaded quickly, thanks to Vite’s optimized build process.
  • Responsiveness: Tested the site on different screen sizes to confirm that Tailwind’s responsive utilities worked as expected.

Why Use the preview Script?

The preview script lets me test the production build locally before deploying it, reducing the risk of errors or broken functionality in the live environment .

Step 8.4: Add Comments to Key Files

To make my production setup easier to understand, I added comments to key files like vite.config.js and tailwind.config.js. For example, here’s a comment I added to my vite.config.js file:

// Vite configuration for All Things Digital
import { defineConfig } from 'vite';
import postcss from './postcss.config.js'; // Reuse my existing PostCSS setup
import ejs from 'vite-plugin-ejs';

export default defineConfig({
  root: './src', // Set the root directory for my source files
  plugins: [ejs()], // Add the EJS plugin for modular components
  build: {
    outDir: '../dist', // Output directory for production builds
    emptyOutDir: true, // Clear the output directory before building
  },
  css: {
    postcss, // Use my existing PostCSS setup
  },
});
Enter fullscreen mode Exit fullscreen mode

Why Add Comments?

Comments make the code easier to understand for future me—or anyone else who might work on the project. They also serve as a reminder of why certain decisions were made .

Step 8.5: Prepare for Deployment

Finally, I prepared the dist folder for deployment. Since the production build includes all necessary assets, I simply uploaded the contents of the dist folder to my hosting provider. Tools like Netlify, Vercel, or GitHub Pages make this process seamless .

Why This Step Matters

Building for production ensures that my site is optimized for performance, with minimal file sizes and no unnecessary code. By verifying the output and testing the production build locally, I gained confidence in the stability and performance of my site .

For further reading on optimizing production builds, I recommend checking out this guide on Tyk Self-Managed .

In conclusion, integrating Vite into my project has been a transformative experience, streamlining my workflow and enhancing both development and production processes . By modularizing my code and leveraging tools like vite-plugin-ejs, I’ve created a scalable, maintainable, and efficient setup that aligns with modern best practices. This journey not only improved my site’s performance but also deepened my understanding of how cutting-edge tools like Vite can elevate web development . With this solid foundation in place, I’m excited to explore even more possibilities in the future .

Final Wrap-up

In conclusion, integrating Vite into my project has been a transformative experience, streamlining my workflow and enhancing both development and production processes . By modularizing my code and leveraging tools like vite-plugin-ejs, I’ve created a scalable, maintainable, and efficient setup that aligns with modern best practices. This journey not only improved my site’s performance but also deepened my understanding of how cutting-edge tools like Vite can elevate web development . With this solid foundation in place, I’m excited to explore even more possibilities in the future.

Redis image

Short-term memory for faster
AI agents 🤖💨

AI agents struggle with latency and context switching. Redis fixes it with a fast, in-memory layer for short-term context—plus native support for vectors and semi-structured data to keep real-time workflows on track.

Start building

Top comments (0)

Gen AI apps are built with MongoDB Atlas

Gen AI apps are built with MongoDB Atlas

MongoDB Atlas is the developer-friendly database for building, scaling, and running gen AI & LLM apps—no separate vector DB needed. Enjoy native vector search, 115+ regions, and flexible document modeling. Build AI faster, all in one place.

Start Free