Skip to main content
01

Tired of Vue Boilerplate? Here's My Clean, Fast Setup

·2 min read
Vue.jsViteTypeScriptDeveloper ExperienceFrontend

After setting up Vue projects from scratch for clients more times than I can count — installing dependencies, configuring ESLint, setting up TypeScript, adding Prettier — I built a streamlined starter that skips the repetitive work.

The Pain Points

If you've worked with Vue, you know the drill. You run create-vue or vite create, and then spend the next hour customizing the setup:

  1. Adding proper TypeScript configuration that actually works with Vue
  2. Setting up linting rules that don't drive you crazy
  3. Configuring file-based routing because manually defining routes is so 2018
  4. Integrating UI components that don't need endless styling from scratch
  5. Tweaking performance settings you'll forget about until things slow down

Nuxt works for some projects, but it's often more than what's needed. Sometimes a clean Vue setup without the extra abstraction layer is the right call.

The Setup

Step 1: Create the Base Project

Start with Vite:

bash
bun create vite@latest my-project --template vue-ts
cd my-project
bun install

I use Bun for package management — faster and has built-in TypeScript support. npm/yarn work fine too.

Step 2: Add Prettier

bash
bun add -D prettier prettier-plugin-tailwindcss

Create a .prettierrc:

json
{
  "printWidth": 100,
  "tabWidth": 2,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "endOfLine": "lf",
  "plugins": ["prettier-plugin-tailwindcss"]
}

Add to package.json:

json
"scripts": {
  "format": "prettier --write . --list-different --cache"
}

Step 3: ESLint Configuration

bash
bun add -D eslint @eslint/js globals typescript-eslint eslint-plugin-perfectionist eslint-plugin-prettier eslint-config-prettier eslint-plugin-vue

Create eslint.config.mjs:

javascript
import eslint from "@eslint/js";
import perfectionist from "eslint-plugin-perfectionist";
import prettier from "eslint-plugin-prettier/recommended";
import vue from "eslint-plugin-vue";
import globals from "globals";
import tseslint from "typescript-eslint";
 
const eslintConfig = tseslint.config(
  eslint.configs.recommended,
  perfectionist.configs["recommended-natural"],
  tseslint.configs.recommended,
  {
    extends: [...vue.configs["flat/recommended"]],
    files: ["**/*.{ts,vue}"],
    languageOptions: {
      ecmaVersion: "latest",
      globals: {
        ...globals.browser,
      },
      parserOptions: {
        parser: tseslint.parser,
      },
      sourceType: "module",
    },
    rules: {
      "no-console": ["warn", { allow: ["warn", "error"] }],
      "vue/multi-word-component-names": "off",
    },
  },
  {
    rules: {
      "@typescript-eslint/consistent-type-definitions": ["error", "type"],
      "@typescript-eslint/consistent-type-imports": [
        "error",
        {
          fixStyle: "separate-type-imports",
          prefer: "type-imports",
        },
      ],
      "@typescript-eslint/no-unused-vars": [
        "warn",
        {
          argsIgnorePattern: "^_",
          ignoreRestSiblings: true,
        },
      ],
    },
  },
  prettier,
);
 
export default eslintConfig;

Add to package.json:

json
"scripts": {
  "lint": "eslint src",
  "lint:fix": "eslint src --fix",
  "typecheck": "vue-tsc --noEmit"
}

Step 4: TailwindCSS

bash
bun add tailwindcss @tailwindcss/vite

Update your vite.config.ts:

typescript
import tailwindcss from "@tailwindcss/vite";
import vue from "@vitejs/plugin-vue";
import path from "node:path";
import { defineConfig } from "vite";
 
export default defineConfig({
  plugins: [vue(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    port: 3000,
    hmr: {
      overlay: true,
    },
  },
});

Replace your src/style.css with:

css
@import "tailwindcss";

Step 5: File-Based Routing

bash
bun add vue-router unplugin-vue-router

Update your vite.config.ts again:

typescript
import tailwindcss from "@tailwindcss/vite";
import vue from "@vitejs/plugin-vue";
import path from "node:path";
import router from "unplugin-vue-router/vite";
import { defineConfig } from "vite";
 
export default defineConfig({
  plugins: [
    router(), // Must come before vue()
    vue(),
    tailwindcss(),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    port: 3000,
    hmr: {
      overlay: true,
    },
  },
});

Update your TypeScript configuration in tsconfig.app.json:

json
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "types": ["unplugin-vue-router/client"]
  },
  "include": ["src/**/*.ts", "src/**/*.vue", "./typed-router.d.ts"]
}

Add type references to src/vite-env.d.ts:

typescript
/// <reference types="vite/client" />
/// <reference types="unplugin-vue-router/client" />

Create src/router.ts:

typescript
import { createRouter, createWebHistory } from "vue-router";
import { routes } from "vue-router/auto-routes";
 
export const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
  scrollBehavior(_to, _from, savedPosition) {
    return savedPosition || { top: 0 };
  },
});
 
export default router;

Update src/main.ts to use the router:

typescript
import { createApp } from "vue";
 
import App from "@/App.vue";
import router from "@/router";
import "@/style.css";
 
const app = createApp(App);
 
app.use(router);
app.mount("#app");

Update src/App.vue to include the router view:

vue
<template>
  <RouterView />
</template>

Step 6: Add UI Components with shadcn-vue

To avoid reinventing UI components, let's add shadcn-vue:

bash
bunx --bun shadcn-vue@latest init

When prompted, choose your preferred color scheme (I usually go with Neutral).

Now let's add a button component:

bash
bunx --bun shadcn-vue@latest add button

Create a page file at src/pages/index.vue:

vue
<script setup lang="ts">
import { Button } from "@/components/ui/button";
</script>
 
<template>
  <div class="grid h-dvh place-items-center">
    <Button>Clean Setup Complete!</Button>
  </div>
</template>

What This Gets You

TypeScript That Works

With proper configuration and unplugin-vue-router:

  • Fully typed routes (try useRoute("/users/[id]"))
  • Type checking on your components
  • Auto-completion everywhere it matters

File-Based Routing

Create src/pages/about.vue and it's available at /about. Create src/pages/users/[id].vue for a dynamic route at /users/:id. All typed:

vue
<script setup lang="ts">
import { useRoute } from "vue-router";
 
// This will be perfectly typed, with route.params.id as a string!
const route = useRoute("/users/[id]");
</script>

UI Components

shadcn-vue provides:

  • Accessible components out of the box
  • Consistent styling with Tailwind
  • Customizable design tokens
  • Only the components you need (reducing bundle size)

Performance

Performance tracking in dev, stripped in production:

typescript
import { createApp } from "vue";
 
import App from "@/App.vue";
import router from "@/router";
import "@/style.css";
 
const app = createApp(App);
 
app.config.performance = import.meta.env.DEV;
 
app.config.compilerOptions = {
  comments: false,
  whitespace: "condense",
};
 
if (import.meta.env.PROD) {
  app.config.warnHandler = () => null;
}
 
app.use(router);
app.mount("#app");

Results

Used across multiple client projects:

  • Development speed: New features take ~30% less time to implement
  • Bundle size: ~20% smaller than my previous setups
  • Performance: Consistently scoring 95+ on Lighthouse
  • Maintenance: Much easier to onboard new developers

When to Use This

Works well if you want:

  • A lightweight Vue setup without Nuxt's overhead
  • Type safety without the complexity
  • Modern file-based routing
  • Production-ready performance optimizations
  • A component library that won't slow you down

Skip it if:

  • You need SSR/SSG (use Nuxt)
  • You're working with an existing project with different conventions
  • You prefer a different UI approach than Tailwind

For most client projects, this hits the right balance. The template repository is available to clone.