vue-nnn-router
| Install | |
|---|---|
npm install vue-nnn-router |
|
| Latest Version: | 0.1.1 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 20, 2026 |
| Links: | GitHub · npm |
vue-nnn-router
File-based routing cho SPA dùng Vue Router. Chuyển map từ Vite import.meta.glob thành routes có layout, dynamic segment, middleware, redirect, tên route, scroll behavior, progress bar và tùy chọn smooth scrolling bằng Lenis.
English · Tiếng Việt · npm · Changelog
Tính năng
index.vue, page lồng nhau và_layout.vue[id].vuehoặc[id]/index.vue→:id_middleware.tskế thừa theo thư mục_redirect.tslàm page mặc định cho layout- Page mặc định eager-load, tùy chọn lazy-load; middleware và redirect eager-load
- Tên route ổn định và có thể sinh file hằng số
- Scroll khi chuyển route và cấu hình scroll theo từng page
- Progress bar và smooth scrolling liên tục bằng Lenis
Cài đặt
npm install vue-nnn-router
Yêu cầu:
- Vue
^3.3 - Vue Router
^4.2hoặc^5.0 - Vite, hoặc công cụ khác cung cấp map có cấu trúc như
import.meta.glob
Chỉ cần cài Lenis khi dùng smooth scrolling liên tục:
npm install lenis
Bắt đầu nhanh
Tạo các page trong src/pages:
src/pages/
├── index.vue # /
├── about.vue # /about
└── users/
├── _layout.vue # bọc /users/**; cần render <RouterView />
├── _middleware.ts # chạy cho /users/**
├── index.vue # /users
├── add.vue # /users/add
└── [id].vue # /users/:id
Mặc định page, middleware và redirect được eager-load. Thêm plugin vào Vite một lần:
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { vueNnnRouterPlugin } from "vue-nnn-router/vite";
export default defineConfig({
plugins: [vue(), vueNnnRouterPlugin()],
});
Sau đó khởi tạo router bằng đường dẫn thư mục:
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import { createNnnRoutes } from "vue-nnn-router";
const routes = createNnnRoutes("src/pages");
export const router = createRouter({
history: createWebHistory(),
routes,
});
Mount router như bình thường:
// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
Khởi tạo bằng đường dẫn
createNnnRoutes("src/pages", options?) cần vueNnnRouterPlugin(). Đường dẫn phải là chuỗi literal truyền trực tiếp vào hàm và tính từ Vite root; "src/pages", "/src/pages" và "./src/pages" tương đương. Plugin tự đặt routesRoot; options vẫn nhận CreateNnnRoutesOptions:
const routes = createNnnRoutes("src/pages", {
prefix: "app",
silent: true,
});
Page/layout .vue, .tsx, .jsx mặc định được eager-load (lazy: false). Đặt lazy: true để chỉ tải component khi vào route:
const routes = createNnnRoutes("src/pages", { lazy: true });
lazy phải là boolean literal trong object options truyền trực tiếp. Các trường khác vẫn có thể dùng giá trị runtime. Nếu có options từ biến hoặc hàm, dùng { ...options, lazy: true } hoặc { ...options, lazy: false } để plugin xác định chế độ tải lúc build. Tùy chọn này chỉ áp dụng cho API đường dẫn; API glob map giữ nguyên cách tải do glob quyết định.
_middleware.{ts,js} và _redirect.{ts,js} luôn eager-load. Trong chế độ lazy, nếu page export middleware, thêm glob eager vào plugin:
vueNnnRouterPlugin({ eager: ["src/pages/users/add.vue"] });
Không dùng biến hay đường dẫn tính lúc runtime cho cú pháp này. Khi cần glob tùy chỉnh, page .ts/.js hoặc không dùng plugin, API glob map vẫn hoạt động:
import { createNnnModules, createNnnRoutes } from "vue-nnn-router";
const modules = createNnnModules({
views: import.meta.glob("/src/pages/**/*.{vue,tsx,jsx}"),
eager: import.meta.glob("/src/pages/**/_{middleware,redirect}.{ts,js}", {
eager: true,
}),
});
const routes = createNnnRoutes(modules, { routesRoot: "src/pages" });
Quy ước file
| File | Kết quả |
|---|---|
index.vue |
URL của thư mục hiện tại |
about.vue |
Thêm /about |
[id].vue |
Thêm /:id |
[id]/index.vue |
Cũng thêm /:id |
_layout.vue |
Bọc các route bên dưới thư mục |
_middleware.ts |
Chạy trước mọi route bên dưới thư mục |
_redirect.ts |
Redirect một layout không có page index |
Page component có thể dùng .vue, .tsx, .jsx, .ts hoặc .js. Hãy thêm extension bạn sử dụng vào glob views.
File _layout.vue phải render <RouterView />:
<template>
<main class="users-layout">
<RouterView />
</main>
</template>
routesRoot
routesRoot loại bỏ phần không thuộc URL khỏi mỗi glob key:
| Glob | routesRoot |
|---|---|
/src/pages/** |
src/pages |
../pages/** |
../pages |
Key đã có dạng users/index.vue |
bỏ qua tùy chọn này |
Khi truyền glob map /src/pages/**, hãy dùng routesRoot: "src/pages". Khi truyền đường dẫn chuỗi, plugin tự suy ra giá trị này.
Tùy chọn route
createNnnRoutes(modules, {
routesRoot: "src/pages",
prefix: "app",
onDuplicate: "first-wins",
verbose: false,
silent: false,
});
| Tùy chọn | Mặc định | Công dụng |
|---|---|---|
lazy |
false |
Với API đường dẫn: đặt true để lazy-load page/layout |
routesRoot |
— | Loại prefix filesystem khỏi glob key |
prefix |
— | Thêm một URL prefix cho mọi route |
onDuplicate |
"first-wins" |
Xử lý hai file sinh cùng URL; cũng nhận "last-wins" hoặc callback |
verbose |
false |
In bảng path và file đã sinh |
logger |
console.log |
Thay logger dùng cho verbose |
silent |
false |
Ẩn warning và output của verbose |
Middleware và redirect
Middleware thư mục chạy từ cha xuống con, sau đó mới tới middleware của page:
pages/_middleware.ts
→ pages/users/_middleware.ts
→ middleware export từ pages/users/add.vue
Export một guard hoặc một mảng guard:
// src/pages/users/_middleware.ts
import type { NavigationGuard } from "vue-router";
const requireUser: NavigationGuard = (to) => {
if (!hasSession() && to.path !== "/login") return "/login";
};
export default requireUser;
// export default [logVisit, requireUser];
Luôn tải _middleware.ts ở chế độ eager. Export middleware nằm trong page chỉ khả dụng khi chính page đó được eager-load, vì vậy middleware thư mục là lựa chọn phù hợp với page lazy-load.
Dùng _redirect.ts cạnh _layout.vue nếu thư mục không có page index:
// src/pages/users/_redirect.ts
export default "add"; // /users → /users/add
Khi bật prefix, nên dùng target tương đối. Target tuyệt đối như "/users/add" được giữ nguyên.
Tên route
Tên được sinh từ URL:
| URL | route.name |
Key hằng số |
|---|---|---|
/ |
home |
home |
/users/add |
users-add |
usersAdd |
/users/:id |
users-id |
usersId |
Để lấy map tại runtime:
import { createNnnRoutesWithNames } from "vue-nnn-router";
const { routes, routeNames } = createNnnRoutesWithNames(modules, {
routesRoot: "src/pages",
});
router.push({ name: routeNames.usersAdd });
Để tự động sinh file ROUTER_NAME, thêm vueNnnRouterNamesPlugin() vào danh sách plugin đang dùng. Không cần truyền tham số nếu pages nằm trong src/pages:
// vite.config.ts
import { defineConfig } from "vite";
import {
vueNnnRouterPlugin,
vueNnnRouterNamesPlugin,
} from "vue-nnn-router/vite";
export default defineConfig({
plugins: [
vueNnnRouterPlugin(),
vueNnnRouterNamesPlugin(),
],
});
import { ROUTER_NAME } from "./router-name";
router.push({ name: ROUTER_NAME.usersAdd });
Mặc định plugin quét page/layout .vue, .tsx, .jsx và _redirect.{ts,js} trong src/pages, rồi tạo file src/router/router-name.ts.
| Tùy chọn | Mặc định | Ý nghĩa |
|---|---|---|
rootPath |
"src" |
Thư mục gốc, tính từ Vite root |
folderPages |
"pages" |
Thư mục pages bên trong rootPath |
outFile |
"router/router-name.ts" |
File đầu ra bên trong rootPath |
Plugin tự ghép routesRoot = join(rootPath, folderPages) và đường dẫn đầu ra join(rootPath, outFile). Cả ba tùy chọn và object cấu hình đều có thể bỏ qua.
vueNnnRouterPlugin() hỗ trợ cú pháp createNnnRoutes("src/pages"); vueNnnRouterNamesPlugin() sinh file hằng số tên route. Khi cần cả hai chức năng, dùng cả hai plugin như trên.
Có thể chỉ truyền tùy chọn cần đổi:
vueNnnRouterNamesPlugin({ outFile: "constants/router-name.ts" });
// Xuất: src/constants/router-name.ts
Nếu dùng thư mục khác:
vueNnnRouterNamesPlugin({
rootPath: "src/app",
folderPages: "views",
outFile: "constants/router-name.ts",
});
// Quét: src/app/views
// Xuất: src/app/constants/router-name.ts
rootPath: "" cho phép dùng thư mục ngay dưới Vite root. Nếu router dùng prefix, truyền cùng giá trị vào names plugin để tên route khớp nhau; silent vẫn được hỗ trợ và mặc định là true.
Khi chuyển từ API cũ, thay pages/routesRoot bằng rootPath/folderPages, và bỏ phần rootPath khỏi outFile cũ. Plugin dùng Vite root của dự án thay cho tùy chọn root trước đây.
Cuộn trang
Thư viện có hai chức năng riêng biệt:
| Chức năng | Dùng khi |
|---|---|
createNnnScrollBehavior |
Chọn vị trí page sau khi chuyển route: đầu trang, vị trí cũ hoặc hash |
createNnnSmoothScroll |
Làm mượt liên tục theo kiểu Lenis khi người dùng cuộn |
Cuộn sau khi chuyển route
import { createNnnScrollBehavior } from "vue-nnn-router";
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior: createNnnScrollBehavior({
restorePosition: true,
scrollToHash: true,
top: 0,
}),
});
Các tùy chọn:
| Tùy chọn | Mặc định | Công dụng |
|---|---|---|
smooth |
false |
Animate khi cuộn sau chuyển route |
restorePosition |
true |
Khôi phục vị trí khi Back/Forward |
scrollToHash |
true |
Cuộn tới phần tử từ URL hash |
top / left |
0 |
Offset theo pixel |
skipMetaKey |
"noScroll" |
Bỏ qua nếu route meta tương ứng là truthy; dùng false để tắt cơ chế này |
scrollMap |
— | Áp dụng cấu hình riêng đã sinh cho từng page |
Cấu hình scroll theo từng page
Thêm scroll plugin một lần:
// vite.config.ts
import { defineConfig } from "vite";
import { vueNnnRouterScrollPlugin } from "vue-nnn-router/vite";
export default defineConfig({
plugins: [
vueNnnRouterScrollPlugin(),
],
});
Mặc định plugin quét page/layout .vue, .tsx, .jsx trong src/pages và sinh src/router/router-scroll.ts, gồm cả NNN_SCROLL và NNN_SMOOTH_SCROLL.
| Tùy chọn | Mặc định | Ý nghĩa |
|---|---|---|
rootPath |
"src" |
Thư mục gốc, tính từ Vite root |
folderPages |
"pages" |
Thư mục pages bên trong rootPath |
outFile |
"router/router-scroll.ts" |
File đầu ra bên trong rootPath |
silent |
false |
Tắt cảnh báo khi không đọc được cấu hình tĩnh |
Giống names plugin, thư mục quét là join(rootPath, folderPages) và file đầu ra là join(rootPath, outFile). Có thể bỏ qua toàn bộ cấu hình hoặc chỉ truyền tùy chọn cần đổi:
vueNnnRouterScrollPlugin({ outFile: "constants/router-scroll.ts" });
// Xuất: src/constants/router-scroll.ts
vueNnnRouterScrollPlugin({
rootPath: "src/app",
folderPages: "views",
outFile: "constants/router-scroll.ts",
});
// Quét: src/app/views
// Xuất: src/app/constants/router-scroll.ts
rootPath: "" cho phép dùng thư mục ngay dưới Vite root. Khi chuyển từ API cũ, thay root/pages bằng Vite root và rootPath/folderPages, rồi bỏ phần rootPath khỏi outFile cũ.
Truyền navigation map vào router:
import { createNnnScrollBehavior } from "vue-nnn-router";
import { NNN_SCROLL } from "./router-scroll";
scrollBehavior: createNnnScrollBehavior({ scrollMap: NNN_SCROLL });
Sau đó cấu hình một page bằng giá trị literal:
<script setup lang="ts">
import { defineNnnScroll } from "vue-nnn-router";
defineNnnScroll({ top: 80, smooth: true });
// defineNnnScroll(false); // giữ nguyên vị trí hiện tại trên page này
</script>
Smooth scrolling liên tục với Lenis
Cài Lenis và import CSS:
npm install lenis
// src/main.ts
import "lenis/dist/lenis.css";
Khởi tạo controller một lần sau khi tạo router. Scroll plugin phía trên đồng thời sinh NNN_SMOOTH_SCROLL:
import { createNnnSmoothScroll } from "vue-nnn-router/smooth-scroll";
import { NNN_SMOOTH_SCROLL } from "./router-scroll";
const smoothScroll = createNnnSmoothScroll(router, {
disableOnMobile: true,
mobileBreakpoint: 767,
scrollMap: NNN_SMOOTH_SCROLL,
lenisOptions: {
lerp: 0.1,
smoothWheel: true,
},
});
Bật hoặc tắt theo từng page:
<script setup lang="ts">
import { defineNnnSmoothScroll } from "vue-nnn-router";
defineNnnSmoothScroll(false); // page này dùng native scrolling
// Hoặc override cấu hình mobile toàn cục:
// defineNnnSmoothScroll({ enabled: true, disableOnMobile: false });
</script>
| Tùy chọn | Mặc định | Công dụng |
|---|---|---|
enabled |
true |
Trạng thái mặc định cho page không có override |
disableOnMobile |
false |
Hủy Lenis và dùng native scrolling trên mobile |
mobileBreakpoint |
767 |
Chiều rộng mobile tối đa theo pixel |
scrollMap |
— | Áp dụng cấu hình riêng đã sinh cho từng page |
lenisOptions |
{ autoRaf: true } |
Chuyển các tùy chọn sang Lenis |
Controller tự tạo hoặc hủy Lenis khi route và viewport thay đổi. Gọi smoothScroll.destroy() khi dispose router trong HMR, test hoặc micro-frontend.
Cả defineNnnScroll(...) và defineNnnSmoothScroll(...) phải nhận object hoặc boolean literal để Vite plugin có thể đọc được.
Progress bar khi chuyển trang
Progress bar không cần CSS hay dependency bên thứ ba:
import { createNnnProgress } from "vue-nnn-router/progress";
const progress = createNnnProgress(router, {
color: "#ff4d00",
height: 3,
position: "top",
delay: 120,
});
| Tùy chọn | Mặc định | Công dụng |
|---|---|---|
enabled |
true |
Bật progress bar |
color |
"#ff4d00" |
Màu thanh và glow |
height |
3 |
Độ dày CSS; số được hiểu là pixel |
position |
"top" |
"top" hoặc "bottom" |
delay |
120 |
Chờ trước khi hiện để tránh nhấp nháy |
minimumVisible |
0 |
Thời gian hiển thị tối thiểu theo mili giây |
zIndex |
2147483647 |
Thứ tự xếp lớp |
Gọi progress.destroy() khi cần cleanup. Import createNnnProgress từ vue-nnn-router vẫn hoạt động để tương thích ngược.
Các đường dẫn import
| Import | Export chính |
|---|---|
vue-nnn-router |
Sinh route, helper cho module, tên route, navigation scroll và khai báo theo page |
vue-nnn-router/progress |
createNnnProgress |
vue-nnn-router/smooth-scroll |
createNnnSmoothScroll và các type liên quan |
vue-nnn-router/vite |
Plugin routes từ đường dẫn, sinh file tên route và cấu hình scroll |
Mỗi leaf route được sinh có meta.nnnFile, chứa glob key gốc để debug. Package đã bao gồm TypeScript declarations.
Lỗi thường gặp
- URL chứa
/src/pages: đặtroutesRoot: "src/pages"cho glob/src/pages/**. - Page lồng nhau bị trống: kiểm tra
_layout.vuecó render<RouterView />. - Middleware không chạy: tải
_middleware.tsvới{ eager: true }. - Cấu hình scroll theo page không hoạt động: thêm
vueNnnRouterScrollPluginvà dùng argument literal. - Không resolve được Lenis: cài
lenisvà importlenis/dist/lenis.css.
Demo
npm install
npm run demo:install
npm run demo:dev
Mở /smooth-scroll để test Lenis và /about để so sánh với native scrolling.
Giấy phép
MIT — xem LICENSE.
Related Packages
Router based layout plugin for Vite 8, Vue 3 and Vue Router 5.
A vue.js plugin to implement a middleware pipeline between your routes.
Declarative, layout-based route middleware for Nuxt 3/4, with glob matching, nam...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.1 | ^3.3.0 | MIT | |
| 0.1.0 | ^3.3.0 | MIT | |
| 0.0.9 | ^3.3.0 | MIT | |
| 0.0.8 | ^3.3.0 | MIT | |
| 0.0.7 | ^3.3.0 | MIT | |
| 0.0.6 | ^3.3.0 | MIT | |
| 0.0.5 | ^3.3.0 | MIT | |
| 0.0.4 | ^3.3.0 | MIT | |
| 0.0.3 | ^3.3.0 | MIT | |
| 0.0.2 | ^3.3.0 | MIT | |
| 0.0.1 | ^3.3.0 | MIT |