컴퓨터와 책과 연필
블로그 프사

컴퓨터와 책과 연필

Kim Evergood

허접 개발자의 개인 블로그😘 문서화된 작업은 나의 얼굴. 문서화된 공부는 맞춤 교재.

Vue 라우터 모든 vue 컴포넌트 자동 등록

2025. 8. 29. Kim Evergood이가 씀.

문제

Vue 프로젝트에서 홈을 비롯해 여러 Vue 파일을 매칭시킨다.

import { createRouter, createWebHistory } from "vue-router";

import Home from "@/views/HomeView.vue";
import AAA from "@/views/AAAView.vue";
import BBB from "@/views/BBBView.vue";
import CAbc from "@/views/c/AbcView.vue";
import CDef from "@/views/c/DefView.vue";

const routes = [
  { name: "home", path: "/", component: Home },
  { name: "aaa", path: "/aaa", component: AAA },
  { name: "bbb", path: "/bbb", component: BBB },
  { name: "c-abc", path: "/c/abc", component: CAbc },
  { name: "c-def", path: "/c/def", component: CDef },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

페이지 수가 많아질 수록 비슷한 코드가 많아진다.

풀이

그냥 /views 디렉토리 하위 Vue 파일들의 경로와 파일 이름을 이용해서 그 구조 그대로 등록해도 되지 않을까 생각이 든다.

import { createRouter, createWebHistory } from "vue-router";

/*
 * 이름이 "View.vue"로 끝나는 모든 컴포넌트를 자동으로 라우트로 등록
 */
const vueFiles = import.meta.glob("@/views/**/*View.vue", { eager: true });
const routes = Object.keys(vueFiles).map((filePath) => {
  const temp = filePath
      .replace('/src/views/', '') // 상대경로로
      .replace(/View\.vue$/, '')  // "View.vue" 제거
      .replace(/\/$/, '')         // "/" 제거 (파일명이 View.vue 인 경우)
      .toLowerCase();
  const name = temp === '' ? 'home' : temp.replace(/\//g, '-');
  const path = '/' + temp;
  return {
    name,
    path,
    component: (vueFiles as any)[filePath].default,
  };
});

const router = createRouter({
  history: createWebHistory(),
  routes,
});

※ 이 경우 .../aaa/bbbView.vue 파일과 .../aaa/bbb/View.vue 파일이 있다면 중복이 된다.

없는 페이지

거기에 '없는 페이지' 페이지만 특별히 추가함.

import NotFound from '@/views/NotFound.vue';

const routes = Object.keys(vueFiles).map((filePath) => {
  // ... 아까거
});
routes.push(// 없는 페이지 추가
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: NotFound,
    meta: {
      requiresAuth: false
    },
  }
);

경로 파라미터

파일이름의 괄호를 경로 파라미터로 해석하도록 하여 경로 파라미터를 추가했다. 예: 파일 이름이 Some(id)View.vue이면 some/:id 경로로 매칭됨.

경로 문자열 처리에서 간단히 정규표현식 추가.

const routes = Object.keys(vueFiles).map((filePath) => {
  const temp = filePath
    .replace('/src/views/', '')      // 상대경로로
    .replace(/View\.vue$/, '')       // 파일명 끝 "View.vue" 제거
    .replace(/\(([^)]+)\)/g, "/:$1") // 괄호로 경로 파라미터 지정: '(param)' --> '/:param'
    .replace(/\/$/, '')              // "/" 제거 (파일명이 View.vue 인 경우)
    .toLowerCase();

  // ... 아까거 반복
});

현재 내 코드

import { createRouter, createWebHistory } from "vue-router";

import { useAuthStore } from "@/stores/auth";

import NotFound from '@/views/NotFound.vue';

//// 인증이 필요한 라우트 목록
const authRequiredRoutes = [
  '/board/post/write',
  '/member/myplace',
];

/*
 * 이름이 "View.vue"로 끝나는 모든 컴포넌트를 자동으로 라우트로 등록
 */
const vueFiles = import.meta.glob("@/views/**/*View.vue", { eager: true });
const routes = Object.keys(vueFiles).map((filePath) => {
  const temp = filePath
    .replace('/src/views/', '')      // 상대경로로
    .replace(/View\.vue$/, '')       // 파일명 끝 "View.vue" 제거
    .replace(/\(([^)]+)\)/g, "/:$1") // 괄호로 경로 파라미터 지정: '(param)' --> '/:param'
    .replace(/\/$/, '')              // "/" 제거 (파일명이 View.vue 인 경우)
    .toLowerCase();

  const name = temp === '' ? 'home' : temp.replace(/\//g, '-');
  const path = '/' + temp;
  const requiresAuth = authRequiredRoutes.includes(path);
  console.log(`★router - register: ${name} --> ${path}`);

  return {
    name,
    path,
    component: (vueFiles as any)[filePath].default,
    meta: { requiresAuth }
  };
});
routes.push(// 없는 페이지
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: NotFound,
    meta: {
      requiresAuth: false
    },
  }
);

const router = createRouter({
  history: createWebHistory(),
  routes,
});

// 전역 가드
router.beforeEach((to, from) => {

  // TEST
  console.log(`★router: ${from.fullPath} --> ${to.fullPath}`);

  const authStore = useAuthStore();

  if (authStore.isLoggedIn) {// 로그인 상태
    if (to.name === "memberLogin") {// 로그인페이지 --> 이전 페이지 or 홈
      if (window.history.length > 1) {
        return { path: from.fullPath };
      } else {
        return { name: "home" };// 이전 페이지 없음 --> 홈
      }
    }
  } else {// 비로그인 상태
    if (to.meta.requiresAuth) {// 로그인이 필요한 페이지
      return { name: 'member-login', query: { redirect: to.fullPath } };
    }
  }
});

export default router;

참고

728x90