惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
A
About on SuperTechFans
博客园 - Franky
Engineering at Meta
Engineering at Meta
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
B
Blog
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
Martin Fowler
Martin Fowler
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
博客园 - 叶小钗
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
The Cloudflare Blog
Last Week in AI
Last Week in AI
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
爱范儿
爱范儿
V
V2EX
G
Google Developers Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
⚡ Advanced Path Aliases in Vite — Stop Writing ../../ For...
PRABHANSH TI · 2026-04-26 · via DEV Community

⚡ Advanced Path Aliases in Vite — Stop Writing ../../ Forever

Clean imports aren't just aesthetics — they're architecture.


Hero Banner


🧭 The Problem Nobody Talks About

You're deep in your Vite project. Files are nested. Logic is split. Components are modular. You're doing everything right — and yet, every single import looks like this:

import Button from "../../../components/ui/Button";
import useAuth from "../../hooks/useAuth";
import { formatDate } from "../../../../utils/dateHelper";

Enter fullscreen mode Exit fullscreen mode

Three dots. Four dots. Five dots.

It's messy, it's fragile, and when you refactor even one folder, everything breaks.

There's a better way. It's called Advanced Path Aliases — and in Vite, it's surprisingly simple to set up.


🎯 What Are Path Aliases?

A path alias is a shorthand you define to replace a long, relative path. Instead of traversing directories with ../../, you map a symbol (like @components) directly to a folder.

Think of it like a bookmark. You define it once, and use it everywhere.

Before Alias After Alias
../../../components/Button @components/Button
../../hooks/useAuth @hooks/useAuth
../../../../utils/format @utils/format

Clean. Predictable. Refactor-proof. ✅


🏗️ Project Structure We're Working With

Before diving into config, here's a clean, real-world Vite + React project structure this guide targets:

my-vite-app/
├── public/
├── src/
│   ├── assets/
│   │   └── logo.svg
│   ├── components/
│   │   ├── ui/
│   │   │   └── Button.tsx
│   │   └── layout/
│   │       └── Navbar.tsx
│   ├── hooks/
│   │   ├── useAuth.ts
│   │   └── useFetch.ts
│   ├── pages/
│   │   ├── Home.tsx
│   │   └── Dashboard.tsx
│   ├── utils/
│   │   ├── formatDate.ts
│   │   └── apiHelper.ts
│   ├── services/
│   │   └── authService.ts
│   ├── store/
│   │   └── useStore.ts
│   ├── types/
│   │   └── index.d.ts
│   ├── App.tsx
│   └── main.tsx
├── index.html
├── vite.config.ts
└── tsconfig.json

Enter fullscreen mode Exit fullscreen mode

💡 Note: This guide uses TypeScript. If you're using plain JavaScript, the concepts are identical — just skip the tsconfig.json parts.


⚙️ Step 1 — Configure Vite (vite.config.ts)

Open your vite.config.ts file and add the resolve.alias section:

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@components": path.resolve(__dirname, "src/components"),
      "@hooks":      path.resolve(__dirname, "src/hooks"),
      "@utils":      path.resolve(__dirname, "src/utils"),
      "@pages":      path.resolve(__dirname, "src/pages"),
      "@assets":     path.resolve(__dirname, "src/assets"),
      "@services":   path.resolve(__dirname, "src/services"),
      "@store":      path.resolve(__dirname, "src/store"),
      "@types":      path.resolve(__dirname, "src/types"),
    },
  },
});

Enter fullscreen mode Exit fullscreen mode

That's it for Vite. But wait — there's one more piece for TypeScript users.


🧠 Step 2 — Configure TypeScript (tsconfig.json)

Vite knows about your aliases now, but TypeScript doesn't. Your IDE will throw red squiggles everywhere unless you also update tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@components/*": ["src/components/*"],
      "@hooks/*":      ["src/hooks/*"],
      "@utils/*":      ["src/utils/*"],
      "@pages/*":      ["src/pages/*"],
      "@assets/*":     ["src/assets/*"],
      "@services/*":   ["src/services/*"],
      "@store/*":      ["src/store/*"],
      "@types/*":      ["src/types/*"]
    }
  },
  "include": ["src"]
}

Enter fullscreen mode Exit fullscreen mode

⚠️ Important: The paths in tsconfig.json and the alias in vite.config.ts must always stay in sync. A mismatch means Vite builds fine, but TypeScript screams.


🚀 Step 3 — Install the path Types (If Needed)

If TypeScript complains about __dirname or the path module, install the Node types:

npm install --save-dev @types/node

Enter fullscreen mode Exit fullscreen mode

Then update your tsconfig.json (if not already):

{
  "compilerOptions": {
    "types": ["node"]
  }
}

Enter fullscreen mode Exit fullscreen mode


✨ Step 4 — Using Your Aliases in the Wild

Now for the fun part. Here's what real usage looks like across different file types:

📦 Component Imports

// Before 😩
import Button from "../../../components/ui/Button";
import Navbar from "../../components/layout/Navbar";

// After 🎉
import Button from "@components/ui/Button";
import Navbar from "@components/layout/Navbar";

Enter fullscreen mode Exit fullscreen mode


🪝 Custom Hook Imports

// Before 😩
import useAuth from "../../hooks/useAuth";
import useFetch from "../../../hooks/useFetch";

// After 🎉
import useAuth from "@hooks/useAuth";
import useFetch from "@hooks/useFetch";

Enter fullscreen mode Exit fullscreen mode


🛠️ Utility Imports

// Before 😩
import { formatDate } from "../../../../utils/formatDate";
import { apiHelper } from "../utils/apiHelper";

// After 🎉
import { formatDate } from "@utils/formatDate";
import { apiHelper } from "@utils/apiHelper";

Enter fullscreen mode Exit fullscreen mode


🎨 Asset Imports

// Before 😩
import logo from "../../assets/logo.svg";

// After 🎉
import logo from "@assets/logo.svg";

Enter fullscreen mode Exit fullscreen mode


🧩 A Real Component — Before vs After

Let's look at a realistic Dashboard component:

// ❌ Dashboard.tsx — BEFORE (nightmare imports)
import React from "react";
import Navbar from "../../components/layout/Navbar";
import Button from "../../components/ui/Button";
import useAuth from "../../hooks/useAuth";
import useFetch from "../../hooks/useFetch";
import { formatDate } from "../../utils/formatDate";
import { fetchUser } from "../../services/authService";
import logo from "../../assets/logo.svg";

export default function Dashboard() {
  const { user } = useAuth();
  // ...
}

Enter fullscreen mode Exit fullscreen mode

// ✅ Dashboard.tsx — AFTER (clean & readable)
import React from "react";
import Navbar from "@components/layout/Navbar";
import Button from "@components/ui/Button";
import useAuth from "@hooks/useAuth";
import useFetch from "@hooks/useFetch";
import { formatDate } from "@utils/formatDate";
import { fetchUser } from "@services/authService";
import logo from "@assets/logo.svg";

export default function Dashboard() {
  const { user } = useAuth();
  // ...
}

Enter fullscreen mode Exit fullscreen mode

The logic is identical — but now a new developer can glance at the imports and instantly understand the architecture.


🔄 Advanced Pattern: Barrel Files + Aliases

Take aliases to the next level by combining them with barrel files (index.ts):

// src/components/ui/index.ts  (barrel file)
export { default as Button } from "./Button";
export { default as Input } from "./Input";
export { default as Modal } from "./Modal";
export { default as Card } from "./Card";

Enter fullscreen mode Exit fullscreen mode

Now you can import multiple UI components in a single line:

// 🚀 One import to rule them all
import { Button, Input, Modal, Card } from "@components/ui";

Enter fullscreen mode Exit fullscreen mode

This pattern is especially powerful for design systems and component libraries.


🛡️ Pro Tips & Best Practices

1. Prefix Everything with @

The @ prefix is the community convention. It signals "this is an alias, not a package."

// ✅ Conventional
"@components/*"

// ⚠️ Possible but unusual
"~components/*"
"components/*"  // ← Can conflict with npm packages!

Enter fullscreen mode Exit fullscreen mode


2. Keep Aliases Shallow

Map to top-level folders, not deeply nested ones:

// ✅ Alias to top-level folder
"@components": "src/components"

// ❌ Over-aliasing — gets confusing fast
"@uiButtons": "src/components/ui/buttons"

Enter fullscreen mode Exit fullscreen mode

Let the path after the alias carry the rest of the depth.


3. Document Your Aliases

Add a comment block to vite.config.ts so your team always knows what's available:

resolve: {
  alias: {
    // 📦 UI Components — import { Button } from "@components/ui"
    "@components": path.resolve(__dirname, "src/components"),
    // 🪝 Custom Hooks — import useAuth from "@hooks/useAuth"
    "@hooks":      path.resolve(__dirname, "src/hooks"),
    // 🛠️ Utilities — import { formatDate } from "@utils/formatDate"
    "@utils":      path.resolve(__dirname, "src/utils"),
    // 🌐 API Services — import { authService } from "@services/auth"
    "@services":   path.resolve(__dirname, "src/services"),
  },
},

Enter fullscreen mode Exit fullscreen mode


4. Keep vite.config.ts and tsconfig.json in Sync (Automate It)

Install vite-tsconfig-paths to automatically sync Vite aliases with TypeScript paths — no duplicate config:

npm install --save-dev vite-tsconfig-paths

Enter fullscreen mode Exit fullscreen mode

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [react(), tsconfigPaths()], // ← just add this!
});

Enter fullscreen mode Exit fullscreen mode

Now you only manage aliases in tsconfig.json. Vite reads them automatically. 🎯


🧪 Testing With Aliases (Vitest / Jest)

Don't forget your test runner! Aliases must also be configured for tests.

Vitest (Native Vite Testing)

Vitest automatically inherits your Vite config — no extra setup needed. ✅

// vitest.config.ts (optional — just extend vite.config.ts)
import { defineConfig } from "vitest/config";
import { alias } from "./vite.config"; // reuse your aliases

Enter fullscreen mode Exit fullscreen mode

Jest

For Jest, add a moduleNameMapper to jest.config.js:

// jest.config.js
module.exports = {
  moduleNameMapper: {
    "^@components/(.*)$": "<rootDir>/src/components/$1",
    "^@hooks/(.*)$":      "<rootDir>/src/hooks/$1",
    "^@utils/(.*)$":      "<rootDir>/src/utils/$1",
    "^@services/(.*)$":   "<rootDir>/src/services/$1",
  },
};

Enter fullscreen mode Exit fullscreen mode


🌍 ESLint Integration

If you use eslint-plugin-import, tell it about your aliases:

npm install --save-dev eslint-import-resolver-typescript

Enter fullscreen mode Exit fullscreen mode

// .eslintrc.json
{
  "settings": {
    "import/resolver": {
      "typescript": {
        "alwaysTryTypes": true,
        "project": "./tsconfig.json"
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Now ESLint respects your aliases for import-order rules. No more false positives. ✅


📊 The Full Config Cheat Sheet

Here's your complete, copy-paste-ready setup:

vite.config.ts

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@components": path.resolve(__dirname, "src/components"),
      "@hooks":      path.resolve(__dirname, "src/hooks"),
      "@utils":      path.resolve(__dirname, "src/utils"),
      "@pages":      path.resolve(__dirname, "src/pages"),
      "@assets":     path.resolve(__dirname, "src/assets"),
      "@services":   path.resolve(__dirname, "src/services"),
      "@store":      path.resolve(__dirname, "src/store"),
      "@types":      path.resolve(__dirname, "src/types"),
    },
  },
});

Enter fullscreen mode Exit fullscreen mode

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["src/components/*"],
      "@hooks/*":      ["src/hooks/*"],
      "@utils/*":      ["src/utils/*"],
      "@pages/*":      ["src/pages/*"],
      "@assets/*":     ["src/assets/*"],
      "@services/*":   ["src/services/*"],
      "@store/*":      ["src/store/*"],
      "@types/*":      ["src/types/*"]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode


🏁 Summary

Here's everything covered in one glance:

Step What You Did
1 Added resolve.alias to vite.config.ts
2 Mirrored paths in tsconfig.json for TypeScript
3 Installed @types/node for __dirname support
4 Replaced all ../../ imports with @alias/
5 Combined with barrel files for super clean imports
6 Optionally used vite-tsconfig-paths to DRY up config
7 Configured test runners and ESLint to understand aliases

💬 Final Thoughts

Advanced path aliases are one of those things that feel like a minor developer-experience tweak — until you use them for a week and can never go back.

Your imports become self-documenting. New teammates onboard faster. Refactoring is safer. Your codebase reads like prose instead of a directory traversal.

"Code is read far more often than it is written."
— Robert C. Martin

Set up your aliases today. Future-you will send a thank-you note. 🙏


Found this helpful? Share it with your team — especially the one who still writes ../../../../.