Understanding the v2 Frontend Architecture of RomM: A Complete Technical Guide

RomM's v2 frontend is a modern Vue 3 single-page application built with TypeScript that implements a layered, feature-oriented architecture separating presentation, state, data, and infrastructure concerns.

The RomM project is an open-source game library manager and ROM organizer. Its v2 frontend represents a complete architectural overhaul designed for scalability, maintainability, and multi-modal interaction (desktop, mobile, and TV/gamepad). According to the RomM source code, the architecture follows strict separation of concerns across four distinct layers while leveraging modern Vue 3 patterns like the Composition API and Pinia for state management.

Layered Architecture Overview

The v2 frontend architecture divides responsibilities into four clean layers:

Layer Purpose Key Locations
Presentation Page components, layouts, and reusable UI src/views/, src/layouts/, src/components/
State Global reactive state and shared logic src/stores/ (Pinia), src/composables/
Data API clients, caching, and real-time communication src/services/api/, src/services/cache/, src/services/socket.ts
Infrastructure Vue plugins, theming, routing, and i18n src/plugins/, src/router/routes.ts, src/styles/, src/locales/

This layered approach ensures that business logic remains decoupled from UI components, making the codebase testable and extensible.

Application Bootstrap and Plugin System

The entry point src/main.ts initializes the Vue application and wires together all infrastructure concerns before mounting to the DOM.

// src/main.ts
import { createApp } from 'vue';
import RomM from './RomM.vue';
import router from './plugins/router';
import pinia from './plugins/pinia';
import vuetify from './plugins/vuetify';
import i18n from './plugins/i18n';
import mitt from './plugins/mitt';

const app = createApp(RomM);
app.use(vuetify);
app.use(pinia);
app.use(router);
app.use(i18n);
app.use(mitt);
app.mount('#app');

The plugin registration follows a specific order: Vuetify (UI components), Pinia (state), Vue Router (navigation), Vue i18n (localization), and Mitt (event bus). Each plugin lives in src/plugins/ and is configured with type-safe augmentations. For example, src/plugins/pinia.ts injects the router instance into every store:

// src/plugins/pinia.ts
import { createPinia } from 'pinia';
import { useRouter } from 'vue-router';

const pinia = createPinia();
pinia.use(({ store }) => {
  store.$router = useRouter();
});
export default pinia;

State Management with Pinia

The architecture implements 18 Pinia stores that manage everything from ROM data and platform metadata to UI navigation state and user preferences. Stores are colocated in src/stores/ and follow a consistent pattern with typed state, getters, and actions.

The roms store demonstrates the pattern for data-heavy domains:

// src/stores/roms.ts (simplified)
export const useRomsStore = defineStore('roms', {
  state: () => ({
    _allRoms: [] as SimpleRom[],
    currentPlatform: null as Platform | null,
    currentCollection: null as Collection | null,
    // pagination and selection state...
  }),
  actions: {
    async fetchRoms(params) {
      const data = await romApi.getRoms(params);
      this._allRoms = data.results;
      // update pagination cursors...
    },
  },
});

Composables in src/composables/ handle cross-cutting concerns like persisting UI settings to localStorage and the backend simultaneously:

// src/composables/useUISettings.ts (pattern)
export function useUISettings() {
  const store = useConfigStore();
  watch(
    () => store.uiSettings,
    (newSettings) => {
      localStorage.setItem('uiSettings', JSON.stringify(newSettings));
      api.put('/users/me', { ui_settings: newSettings });
    },
    { deep: true }
  );
}

Routing and Navigation Architecture

The router configuration in src/router/routes.ts defines 36 named routes organized by layout context. The architecture supports three distinct layout modes:

  • AuthLayout (/setup, /login, /reset-password) – Public authentication flows
  • MainLayout (/, /search, /platform/:platform, /rom/:rom) – Standard desktop/mobile interface
  • ConsoleLayout (/console, /console/rom/:rom/play) – TV-optimized, gamepad-navigable interface

Route guards enforce authentication and scope validation while pre-fetching data. The ROM details route illustrates the pattern:

// src/router/routes.ts (excerpt)
{
  path: '/rom/:rom',
  name: 'RomDetails',
  component: () => import('../views/GameDetails.vue'),
  beforeEnter: async (to, from, next) => {
    await romsStore.fetchRom(to.params.rom as string);
    next();
  },
},

Data Layer and API Communication

All server communication flows through a centralized Axios instance in src/services/api/index.ts. This instance handles CSRF token injection, request tracking, and global error handling (including 403 redirects).

// src/services/api/index.ts
import axios from 'axios';
export const api = axios.create({
  baseURL: '/api',
  timeout: 120_000,
});

Service modules (src/services/api/rom.ts, platform.ts, collection.ts) expose typed CRUD methods. For long-running operations like library scans and file uploads, the architecture uses a Socket.IO client (src/services/socket.ts) with lazy initialization:

// src/services/socket.ts
import { io } from 'socket.io-client';
export const socket = io({ 
  path: '/ws/socket.io/', 
  transports: ['websocket', 'polling'] 
});

An experimental stale-while-revalidate cache layer in src/services/cache/ provides optional client-side caching for API responses.

Component Architecture

The UI follows a feature-based hybrid organization with three tiers:

  1. Common components – Shared across features (collection cards, dialogs, navigation bars)
  2. Feature-specific components – Organized by domain (gallery, details, scan, settings)
  3. Console mode components – TV-optimized versions under src/console/

This structure keeps related components colocated while maintaining a shared component library for consistency.

Theming and Internationalization

Vuetify provides the Material Design foundation, augmented by custom design tokens in src/styles/themes.ts and src/styles/tokens.css. Tailwind CSS utilities handle rapid layout styling alongside Vuetify's component system.

Internationalization supports 17 language packs loaded dynamically from src/locales/. The Vue i18n plugin loads JSON files per feature (e.g., gallery.json, home.json) at runtime:

// src/plugins/i18n.ts (pattern)
import { createI18n } from 'vue-i18n';
const i18n = createI18n({
  locale: 'en_US',
  fallbackLocale: 'en_US',
  messages: await importLocaleMessages(),
});

Build Tooling and Development

The frontend builds with Vite 6, utilizing plugins for Tailwind CSS, Vuetify auto-import, PWA generation, and optional HTTPS development. The vite.config.js proxies API and WebSocket traffic to the backend during development, enabling seamless full-stack local testing.

Summary

  • RomM's v2 frontend uses Vue 3 with TypeScript and the Composition API for type-safe, modern reactive development.
  • Pinia manages global state across 18 specialized stores, with router injection for navigation-aware state.
  • The layered architecture cleanly separates presentation (src/components/), state (src/stores/), data (src/services/), and infrastructure (src/plugins/).
  • Three layout modes (Auth, Main, Console) support distinct user contexts from setup to TV gaming.
  • Real-time features use Socket.IO while standard API calls use a centralized Axios instance with CSRF protection.
  • Vite 6 powers the build system with Tailwind CSS and Vuetify for styling.

Frequently Asked Questions

What technology stack does the RomM v2 frontend use?

The RomM v2 frontend is built with Vue 3, TypeScript, and Vite 6. It uses Pinia for state management, Vue Router for navigation, Axios for HTTP requests, and Socket.IO for real-time communication. Styling combines Vuetify (Material Design components) with Tailwind CSS utilities.

How does RomM handle real-time updates during scans and uploads?

Long-running tasks like library scans and file uploads use a Socket.IO client located in src/services/socket.ts. The client connects lazily only when needed and listens for server-sent events to update progress indicators in the UI without polling.

What is the difference between MainLayout and ConsoleLayout?

MainLayout is the standard responsive interface for desktop and mobile browsers, while ConsoleLayout is a specialized TV-optimized interface designed for gamepad navigation. ConsoleLayout features larger hit targets, simplified navigation patterns, and is accessible under /console routes.

How is state managed across the RomM frontend application?

State is managed through 18 Pinia stores in src/stores/ that handle domain data (ROMs, platforms, collections), UI state (navigation, gallery view), and operational tasks (scanning, uploads). The architecture also uses composables in src/composables/ for reusable stateful logic and cross-cutting concerns like settings persistence.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →