After a couple years of building Vue applications for clients, I've lost count of how many times I've set up new projects from scratch. Each time, I found myself repeating the same steps: installing dependencies, configuring ESLint, setting up TypeScript, adding Prettier, and on and on...
Sound familiar?
That's why I finally took the time to create a streamlined Vue setup that cuts through the noise and lets me focus on what matters: building great applications for clients. Today, I'm sharing this battle-tested setup with you.
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:
- Adding proper TypeScript configuration that actually works with Vue
- Setting up linting rules that don't drive you crazy
- Configuring file-based routing because manually defining routes is so 2018
- Integrating UI components that don't need endless styling from scratch
- Tweaking performance settings you'll forget about until things slow down
Sure, you could use Nuxt—and sometimes I do—but for many projects, it's overkill. Sometimes you just need a clean, fast Vue setup without the extra abstractions.
My Clean Setup in 5 Minutes
Here's how I can get a production-ready Vue project running in about 5 minutes. No jokes.
Step 1: Create the Base Project
I start with Vite because it's blazing fast:
bun create vite@latest my-project --template vue-ts
cd my-project
bun install
I've switched to using Bun instead of npm/yarn for package management—it's significantly faster and has built-in TypeScript support. But npm/yarn work just fine too!
Step 2: Add Prettier (Because Life's Too Short for Formatting Debates)
bun add -D prettier prettier-plugin-tailwindcss
Create a .prettierrc
:
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}
Add to package.json
:
"scripts": {
"format": "prettier --write . --list-different --cache"
}
Step 3: ESLint Configuration That Won't Make You Pull Your Hair Out
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
with a sensible configuration that works well with Vue, TypeScript, and won't bombard you with warnings:
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
:
"scripts": {
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"typecheck": "vue-tsc --noEmit"
}
Step 4: Setup TailwindCSS (Because Custom CSS Is a Time Sink)
bun add tailwindcss @tailwindcss/vite
Update your vite.config.ts
:
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:
@import "tailwindcss";
Step 5: Add File-Based Routing (The Game Changer)
This is where things get good. Let's add automatic file-based routing:
bun add vue-router unplugin-vue-router
Update your vite.config.ts
again:
import router from "unplugin-vue-router/vite";
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";
import path from "node:path";
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
:
{
"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
:
///
///
Create src/router.ts
:
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:
import "@/style.css";
import App from "@/App.vue";
import router from "@/router";
import { createApp } from "vue";
const app = createApp(App);
// Performance optimization
app.config.performance = import.meta.env.DEV;
// Compiler options for better performance
app.config.compilerOptions = {
comments: false,
whitespace: "condense",
};
// Disable development warnings in production
if (import.meta.env.PROD) {
app.config.warnHandler = () => null;
}
app.use(router);
app.mount("#app");
Update src/App.vue
to include the router view:
<template>
/>
template>
Step 6: Add UI Components with shadcn-vue
To avoid reinventing UI components, let's add shadcn-vue:
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:
bunx --bun shadcn-vue@latest add button
Create a page file at src/pages/index.vue
:
<script setup lang="ts">
import { Button } from "@/components/ui/button";
script>
<template>
class="grid h-screen place-items-center">
Clean Setup Complete!
template>
The Benefits of This Setup
Why do I love this setup so much? Let me count the ways:
1. TypeScript That Actually Works
No more fighting with Vue's type system. With proper configuration and unplugin-vue-router, you get:
- Fully typed routes (try
useRoute("/users/[id]")
) - Type checking on your components
- Auto-completion everywhere it matters
2. File-Based Routing That Makes Sense
Just create a file at src/pages/about.vue
and it's automatically available at /about
. Create src/pages/users/[id].vue
and boom—you have a dynamic route at /users/:id
.
The best part? It's all typed:
<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>
3. A UI System That Doesn't Slow You Down
With shadcn-vue, you get:
- Accessible components out of the box
- Consistent styling with Tailwind
- Customizable design tokens
- Only the components you need (reducing bundle size)
4. Performance Optimizations From Day One
The setup includes performance tracking in development mode and strips out unnecessary bloat in production:
// Performance optimization
app.config.performance = import.meta.env.DEV;
// Compiler options for better performance
app.config.compilerOptions = {
comments: false,
whitespace: "condense",
};
// Disable development warnings in production
if (import.meta.env.PROD) {
app.config.warnHandler = () => null;
}
Real-World Results
This isn't theoretical—I've used this setup on multiple client projects with clear benefits:
- 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
Conclusion: Is This Setup Right for You?
This setup is perfect 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
It might not be ideal if:
- You need SSR/SSG (consider Nuxt instead)
- You're working with an existing project with different conventions
- You prefer a different UI approach than Tailwind
But for most client projects and applications I build, this setup hits the sweet spot between developer experience and performance.
Want to try it yourself? I've put together a template repository that you can clone and start using immediately.
Let me know what you think in the comments!
What's your preferred Vue setup? I'd love to hear about your approach and any tips you might have!