Back to writing

May 14, 2025 · essay

Migrating My Vanilla JS Site to Vue 3 & Tailwind CSS

Transforming an old-school vanilla JavaScript website into this sleek, modern Vue 3 SPA with a dynamic blog, powered by Vite, TypeScript, and Tailwind CSS.

Migrating My Vanilla JS Site to Vue 3 & Tailwind CSS header image

The short version

LLM
My LLM assistant (Google Gemini 2.5 Pro Preview 05-06) was a fantastic sounding board for architectural decisions, component structuring, and debugging tricky Vue 3 reactivity issues. It helped generate 100% of all the code for the entire site.
Why
To revitalize my vanilla JavaScript personal/business website, making it more maintainable, scalable, and modern by leveraging Vue 3, Vite, TypeScript, and Tailwind CSS, and to integrate a new blog section.
Challenge
Structuring a multi-page application feel with Vue Router, managing global state for UI elements (like the mobile menu) using composables, integrating Tailwind CSS effectively, and porting existing content and logic into Vue components while ensuring a smooth user experience.
Outcome
A fully responsive, component-based Vue 3 single-page application (SPA) – this very website! – with a clean, modern design, improved performance, a new dynamic blog, and a much more organized and maintainable codebase.
AI approach
The LLM served as a Vue 3/Tailwind CSS expert, helping to translate vanilla JS concepts to Vue's reactive paradigm, suggesting best practices for component structure, and assisting with router and state management setup via composables.
Learnings
Vue 3's Composition API is powerful for organizing logic. Vite offers an incredible developer experience. Tailwind CSS significantly speeds up UI development. TypeScript adds robustness. Proper component design is key to a scalable Vue app.

Why this project? My previous personal/business website, was built with pure HTML, CSS, and vanilla JavaScript. It was time for a modern overhaul.

The goals were clear:

  • Improve maintainability and scalability.
  • Enhance the developer experience (DX).
  • Modernize the look and feel.
  • Integrate a blog to share my AI development journey.

The tech stack decyzja was crucial. I opted for Vue 3 for its elegant component model and reactive data binding, Vite for its blazing-fast development server and build times, TypeScript for robust type safety, and Tailwind CSS for its utility-first approach to styling. This combination promised a powerful and efficient development workflow.

The Challenge(s): Migrating from a collection of static HTML files to a dynamic Single Page Application (SPA) presented several interesting challenges:

  • Architectural Shift: The biggest hurdle was rethinking the site's structure. Instead of individual HTML pages, I needed to design a component-based architecture managed by Vue Router. This involved identifying reusable UI elements and encapsulating them into Vue components.
  • Component Design: Deciding how to break down the existing UI into logical, reusable Vue components (like headers, footers, sections, cards) took some planning. The Composition API with script setup made this process much cleaner.
  • State Management: For UI elements like the mobile menu, I needed a way to share state across components. Instead of immediately reaching for a full-blown state manager like Pinia, I leveraged Vue's composables (useMobileMenu.ts) for a lightweight and effective solution.
  • Styling Overhaul: Moving from my old custom CSS to Tailwind CSS was a significant change. It required learning the utility-first mindset but quickly paid off in development speed. The @tailwindcss/typography plugin was a lifesaver for styling the blog content.
  • Blog Implementation: Building the blog functionality from scratch was a mini-project within itself. This included creating a data structure for posts (blogPosts.ts), designing components for blog cards (BlogPostCard.vue) and full post content (BlogPostContent.vue), and setting up dynamic routing with Vue Router to handle individual post slugs.
  • TypeScript Integration: Introducing TypeScript meant defining interfaces for props, data structures, and component logic. While it added some initial overhead, the benefits in terms of code clarity and error prevention were well worth it.

The Outcome: The result of this migration is the very website you are currently browsing! It's a fully responsive, performant SPA built with modern web technologies. Key achievements include:

  • A modular, component-based architecture that's much easier to manage and extend.
  • A clean, modern design implemented with Tailwind CSS.
  • Improved performance thanks to Vite and Vue 3's optimizations.
  • A dynamic blog system allowing for easy content updates.
  • A significantly improved developer experience.

The entire codebase is now more organized, robust, and a pleasure to work with.

Screenshot of the new Vue 3 website

Key Learnings:

  • Vue 3 Composition API: It's incredibly powerful for organizing component logic, especially with script setup. Reusable logic can be easily extracted into composables.
  • Vite's Developer Experience: The near-instantaneous hot module replacement (HMR) and fast build times significantly accelerate development.
  • Tailwind CSS: Once you embrace the utility-first approach, building complex UIs becomes remarkably fast. Customizing the tailwind.config.js and using @apply for common component patterns is very effective. The @tailwindcss/typography plugin is fantastic for styling markdown-like content.
  • Vue Router: Essential for SPA navigation. Configuring routes, handling dynamic parameters (like for blog post slugs), and customizing scroll behavior are straightforward.
  • TypeScript with Vue: Provides excellent autocompletion, type checking, and helps catch errors early. Defining interfaces for props (defineProps Props()) and data structures makes components more predictable.
  • Asset Handling in Vite: Importing images and other assets directly into components is seamless. Vite handles the bundling and path resolution.
  • Global vs. Scoped Styles: Finding the right balance between global styles (in src/assets/css/main.css for base styling and Tailwind imports) and Vue's scoped styles for component-specific CSS is important.
Screenshot of the new Vue 3 website

The "AI" Approach: My LLM assistant was an invaluable partner throughout this migration. It helped with:

  • Brainstorming the initial project structure for a Vue 3, Vite, and TypeScript setup.
  • Generating boilerplate code for Vue components and routing configurations.
  • Translating vanilla JavaScript logic and CSS styles into their Vue and Tailwind equivalents.
  • Suggesting layout ideas and Tailwind CSS class combinations for various UI elements.
  • Debugging TypeScript errors, Vue reactivity quirks, and router issues.
  • Explaining Vue 3 concepts and best practices as I encountered them.

This project was an undertaking, and the rewards in terms of a modern, maintainable, and enjoyable codebase are immense. It's a testament to the power of modern frontend tools and a great foundation for future enhancements to workflows.diy!