Skip to main content

Overview

The Modrinth web interface is built with Nuxt 3, providing a server-side rendered (SSR) web application for browsing and managing Minecraft mods and modpacks. Location: apps/frontend/
Framework: Nuxt 3
UI Library: Vue 3 + Tailwind CSS v3
Deployment: Cloudflare Pages

Architecture

Nuxt 3 with SSR

The frontend uses Nuxt 3’s hybrid rendering:
  • Server-Side Rendering (SSR): Pages are rendered on the server for SEO and initial load performance
  • Client-Side Hydration: Vue takes over for interactivity after initial render
  • SPA Navigation: Subsequent navigation is client-side for speed

Data Fetching Flow

Directory Structure

File-Based Routing

Nuxt uses the pages/ directory for automatic routing:

Dynamic Routes

pages/[type]/[id].vue

Components

Website-Specific vs Shared

Website-specific components (src/components/):
  • Admin panels
  • Moderation tools
  • Dashboard widgets
  • Brand-specific components
  • Anything that depends on Nuxt APIs
Shared components (packages/ui/src/components/):
  • Buttons, inputs, modals
  • Project cards
  • Version lists
  • Anything reusable across web and app
Rule of thumb: If it doesn’t depend on Nuxt-specific APIs or website-only features, it belongs in packages/ui.

Component Example

src/components/project/ProjectGallery.vue

Data Fetching

API Client

Use @modrinth/api-client via injectModrinthClient() for all API calls:
The client is provided in src/app.vue:
src/app.vue

TanStack Query

Use TanStack Query (@tanstack/vue-query) for server state management:
See the tanstack-query skill (.claude/skills/tanstack-query/SKILL.md) for patterns and conventions.

Deprecated Composables

These composables are deprecated and should not be used in new code:
  • useAsyncData - Use TanStack Query instead
  • useBaseFetch - Use client.labrinth.* modules instead
  • useServersFetch - Use client.archon.* modules instead

State Management

Pinia Stores

Client-side state is managed with Pinia:
store/auth.ts
Usage:

Server State (TanStack Query)

For data from the API, always use TanStack Query instead of Pinia.

Styling

Tailwind CSS

All styling uses Tailwind CSS with semantic color variables.

Surface Colors (Backgrounds)

Use surface-* variables for backgrounds:

Text Colors

Brand Colors

Example

Never use direct color values like bg-gray-800 or text-white. Always use semantic variables for theme compatibility.

Scoped Styles

Use scoped styles for component-specific CSS:

Layouts

Layouts wrap pages with common UI elements:
layouts/default.vue
Use in pages:
pages/index.vue

Middleware

Route guards run before navigation:
middleware/auth.ts
Use in pages:
pages/dashboard/index.vue

i18n (Internationalization)

The frontend supports 34 languages using FormatJS. Translations: packages/ui/src/locales/

Dependency Injection

Services are provided via Vue’s provide/inject using the createContext pattern:
See the dependency-injection skill (.claude/skills/dependency-injection/SKILL.md) for details.

Development

Running Locally

The website will be available at http://localhost:3000

Hot Module Replacement

Vite provides instant HMR for:
  • Vue components
  • CSS/Tailwind
  • TypeScript/JavaScript
Changes appear in the browser without full page reload.

Environment Variables

.env.local

Building

Development Build

Production Build (Cloudflare Pages)

This uses the Cloudflare Pages Nitro preset.

Pre-PR Checks

Before opening a PR:

Common Patterns

Loading States

Error Handling

Infinite Scrolling

Testing

Component Testing

Tests are located alongside components:
src/components/ProjectCard.test.ts

Run Tests

Next Steps

Desktop App

Learn about the Tauri desktop application

Packages

Explore shared packages and libraries

Local Setup

Set up the complete development environment

Testing

Testing strategies and best practices