Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Dynamic Route Registration in Vue.js Using Backend Permission Data

Tech 1

Role-based access control often requires generating navigation routes dynamically rather than hardcoding them in the frontend. By delegating route definitions to the back end, applications can enforce granular permissions without redeploying the client. The implementation revolves around intercepting navigation, fetching permission data, transforming it into valid route records, and registering them at runtime.

Dynamic Component Resolution

Backend systems cannot transmit Vue component instances. Instead, they provide string identifiers representing file paths. The frontend must map these strings to actual components using dynamic imports. A resolver function handles this conversion:

const resolveModule = (modulePath) => {
  return () => import(`@/pages/${modulePath}.vue`);
};

Base Router Configuration

Initialize the router with static routes that require no authentication, such as login, public pages, and a fallback layout. These routes remain constant regardless of user permissions.

import { createRouter, createWebHistory } from 'vue-router';
import BaseLayout from '@/layouts/BaseLayout.vue';

export const staticRoutes = [
  {
    path: '/auth/login',
    component: () => import('@/pages/auth/Login.vue'),
    meta: { requiresAuth: false }
  },
  {
    path: '/',
    component: BaseLayout,
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/pages/Home.vue'),
        meta: { title: 'Dashboard' }
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    component: () => import('@/pages/errors/NotFound.vue'),
    meta: { hidden: true }
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes: staticRoutes,
  scrollBehavior: () => ({ top: 0 })
});

export default router;

Navigation Guard Interception

Route generation should occur before the user accesses protected views. A global beforeEach guard checks authentication status and determines whether dynamic routes have already been registered. If not, it triggers the fetch and registration process.

import router from '@/router';
import { useAuthStore } from '@/stores/auth';
import { usePermissionStore } from '@/stores/permission';
import { getToken } from '@/utils/auth';

router.beforeEach(async (to, from, next) => {
  const token = getToken();
  const authStore = useAuthStore();
  const permissionStore = usePermissionStore();

  if (!token) {
    return to.path === '/auth/login' ? next() : next('/auth/login');
  }

  if (to.path === '/auth/login') {
    return next('/');
  }

  if (permissionStore.isRoutesLoaded) {
    return next();
  }

  try {
    const accessList = await authStore.fetchUserPermissions();
    const dynamicRoutes = permissionStore.buildRouteTree(accessList);
    
    dynamicRoutes.forEach(route => router.addRoute(route));
    permissionStore.markRoutesLoaded();
    
    next({ ...to, replace: true });
  } catch (error) {
    await authStore.logout();
    next('/auth/login');
  }
});

State Management and Route Transformation

The raw data from the API requires recursive processing to match Vue Router's configuration schema. A dedicated store module handles the transformation, assigns resolved components, and merges the dynamic routes with the static configuration.

import { defineStore } from 'pinia';
import { staticRoutes } from '@/router';
import BaseLayout from '@/layouts/BaseLayout.vue';

export const usePermissionStore = defineStore('permission', {
  state: () => ({
    dynamicRoutes: [],
    isRoutesLoaded: false
  }),
  actions: {
    buildRouteTree(apiData) {
      const transformed = this.transformNodes(apiData);
      this.dynamicRoutes = transformed;
      return transformed;
    },
    transformNodes(nodes) {
      return nodes.map(node => {
        const routeConfig = {
          path: node.path,
          name: node.routeName,
          meta: {
            title: node.label,
            icon: node.icon || 'default-icon',
            requiresAuth: true
          }
        };

        if (node.viewKey === 'BaseLayout') {
          routeConfig.component = BaseLayout;
        } else if (node.viewKey) {
          routeConfig.component = () => import(`@/pages/${node.viewKey}.vue`);
        }

        if (Array.isArray(node.subRoutes) && node.subRoutes.length > 0) {
          routeConfig.children = this.transformNodes(node.subRoutes);
        }

        return routeConfig;
      });
    },
    markRoutesLoaded() {
      this.isRoutesLoaded = true;
    }
  }
});

The recursive transformer iterates through the backend payload, maps string identifiers to lazy-loaded components, attaches metadata, and constructs nested child routes. Once processed, the guard registers each route individually and redirects the user to the originally requested path, ensuring the navigation pipeline recognizes the newly added definitions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.