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

> Explore RomM's v2 frontend architecture a modern Vue 3 TypeScript SPA. Understand its layered feature-oriented design separating presentation state data and infrastructure.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: architecture
- Published: 2026-07-05

---

**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`](https://github.com/rommapp/romm/blob/main/src/services/socket.ts) |
| **Infrastructure** | Vue plugins, theming, routing, and i18n | `src/plugins/`, [`src/router/routes.ts`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/src/main.ts) initializes the Vue application and wires together all infrastructure concerns before mounting to the DOM.

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/src/plugins/pinia.ts) injects the router instance into every store:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/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:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/src/services/api/index.ts). This instance handles CSRF token injection, request tracking, and global error handling (including 403 redirects).

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/src/services/api/rom.ts), [`platform.ts`](https://github.com/rommapp/romm/blob/main/platform.ts), [`collection.ts`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/src/services/socket.ts)) with lazy initialization:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/src/styles/themes.ts) and [`src/styles/tokens.css`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/gallery.json), [`home.json`](https://github.com/rommapp/romm/blob/main/home.json)) at runtime:

```typescript
// 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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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.