> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/go-gitea/gitea/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Architecture

> Frontend build system, Vue.js components, and styling

## Overview

Gitea's frontend is built with Vue.js 3, TypeScript, and Webpack, combining server-rendered templates with reactive components.

## Technology Stack

<CardGroup cols={2}>
  <Card title="Framework" icon="vuejs">
    **Vue.js 3.5+**

    * Composition API
    * Single-file components
    * Reactive state management
  </Card>

  <Card title="Language" icon="code">
    **TypeScript**

    * Type-safe JavaScript
    * Better IDE support
    * Fewer runtime errors
  </Card>

  <Card title="Build Tool" icon="webpack">
    **Webpack 5**

    * Module bundling
    * Asset optimization
    * Code splitting
  </Card>

  <Card title="Package Manager" icon="box">
    **pnpm 10+**

    * Fast, efficient
    * Monorepo support
    * Disk space savings
  </Card>
</CardGroup>

## Directory Structure

```
web_src/
├── js/
│   ├── components/      # Vue components
│   │   ├── RepoActionView.vue
│   │   ├── DiffFileTree.vue
│   │   └── ...
│   ├── features/        # Feature-specific code
│   │   ├── repo-issue.ts
│   │   ├── repo-diff.ts
│   │   └── ...
│   ├── modules/         # Utility modules
│   │   ├── fetch.ts
│   │   ├── toast.ts
│   │   └── ...
│   └── index.ts         # Entry point
├── css/
│   ├── base.css
│   ├── repo.css
│   └── ...
└── svg/                 # SVG icons
```

## Build System

### Development Build

```bash theme={null}
# Install dependencies
pnpm install

# Build frontend (one-time)
make frontend

# Watch mode (auto-rebuild on changes)
make watch-frontend
```

### Production Build

```bash theme={null}
# Optimized production build
NODE_ENV=production make frontend
```

### Build Configuration

Webpack configuration in `webpack.config.ts`:

```typescript theme={null}
export default {
  entry: {
    index: resolve('web_src/js/index.ts'),
  },
  output: {
    path: resolve('public/assets'),
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].js',
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.ts$/,
        use: 'esbuild-loader',
      },
    ],
  },
};
```

## Vue Components

### Component Structure

```vue theme={null}
<script setup lang="ts">
import {ref, computed} from 'vue';

interface Props {
  repoId: number;
  issueId: number;
}

const props = defineProps<Props>();
const emit = defineEmits<{
  update: [id: number];
}>();

const isLoading = ref(false);
const data = ref([]);

const filteredData = computed(() => {
  return data.value.filter(item => item.visible);
});

const loadData = async () => {
  isLoading.value = true;
  try {
    const response = await fetch(`/api/v1/repos/${props.repoId}/issues/${props.issueId}`);
    data.value = await response.json();
  } finally {
    isLoading.value = false;
  }
};
</script>

<template>
  <div class="issue-view">
    <div v-if="isLoading" class="loading">Loading...</div>
    <div v-else>
      <div v-for="item in filteredData" :key="item.id">
        {{ item.title }}
      </div>
    </div>
  </div>
</template>

<style scoped>
.issue-view {
  padding: 1rem;
}

.loading {
  text-align: center;
  color: var(--color-text-light);
}
</style>
```

### Registering Components

```typescript theme={null}
// web_src/js/index.ts
import {createApp} from 'vue';
import RepoActionView from './components/RepoActionView.vue';

// Mount Vue components
for (const el of document.querySelectorAll('.vue-repo-action-view')) {
  const data = el.getAttribute('data-props');
  const props = data ? JSON.parse(data) : {};
  
  createApp(RepoActionView, props).mount(el);
}
```

### Template Integration

```html theme={null}
<!-- templates/repo/actions/view.tmpl -->
<div class="vue-repo-action-view" 
     data-props='{{JsonUtils.EncodeToString .Props}}'>
</div>
```

## Styling

### CSS Architecture

Gitea uses a combination of:

* **Base styles**: Core styling and resets
* **Component styles**: Scoped to Vue components
* **Utility classes**: Tailwind-inspired utilities
* **CSS variables**: For theming

### CSS Variables

```css theme={null}
:root {
  --color-primary: #74ac54;
  --color-text: #0b132a;
  --color-background: #ffffff;
  --spacing-unit: 8px;
  --border-radius: 4px;
}

[data-theme="dark"] {
  --color-text: #e6e9ef;
  --color-background: #1a1a1a;
}
```

### Responsive Design

```css theme={null}
/* Mobile first */
.container {
  padding: 1rem;
}

/* Tablet and up */
@media (min-width: 768px) {
  .container {
    padding: 2rem;
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
  }
}
```

## TypeScript

### Type Definitions

```typescript theme={null}
// web_src/js/types.ts
export interface Repository {
  id: number;
  owner: string;
  name: string;
  full_name: string;
  private: boolean;
  html_url: string;
}

export interface Issue {
  id: number;
  number: number;
  title: string;
  state: 'open' | 'closed';
  user: User;
  created_at: string;
  updated_at: string;
}

export interface User {
  id: number;
  login: string;
  avatar_url: string;
}
```

### API Client

```typescript theme={null}
// web_src/js/modules/fetch.ts
import type {Repository, Issue} from '../types';

export async function fetchRepo(owner: string, repo: string): Promise<Repository> {
  const response = await fetch(`/api/v1/repos/${owner}/${repo}`);
  if (!response.ok) {
    throw new Error(`Failed to fetch repository: ${response.statusText}`);
  }
  return response.json();
}

export async function fetchIssues(owner: string, repo: string): Promise<Issue[]> {
  const response = await fetch(`/api/v1/repos/${owner}/${repo}/issues`);
  if (!response.ok) {
    throw new Error(`Failed to fetch issues: ${response.statusText}`);
  }
  return response.json();
}
```

## State Management

### Reactive Store

```typescript theme={null}
// web_src/js/stores/repo.ts
import {reactive, readonly} from 'vue';
import type {Repository} from '../types';

interface RepoState {
  current: Repository | null;
  isLoading: boolean;
}

const state = reactive<RepoState>({
  current: null,
  isLoading: false,
});

export function useRepoStore() {
  const setRepo = (repo: Repository) => {
    state.current = repo;
  };
  
  const loadRepo = async (owner: string, name: string) => {
    state.isLoading = true;
    try {
      const repo = await fetchRepo(owner, name);
      state.current = repo;
    } finally {
      state.isLoading = false;
    }
  };
  
  return {
    state: readonly(state),
    setRepo,
    loadRepo,
  };
}
```

## Testing

### Unit Tests

```typescript theme={null}
// web_src/js/components/RepoCard.test.ts
import {describe, it, expect} from 'vitest';
import {mount} from '@vue/test-utils';
import RepoCard from './RepoCard.vue';

describe('RepoCard', () => {
  it('renders repository name', () => {
    const wrapper = mount(RepoCard, {
      props: {
        repo: {
          id: 1,
          name: 'gitea',
          owner: 'go-gitea',
        },
      },
    });
    
    expect(wrapper.text()).toContain('gitea');
  });
});
```

### E2E Tests

Playwright tests in `tests/e2e/`:

```typescript theme={null}
import {test, expect} from '@playwright/test';

test('create repository', async ({page}) => {
  await page.goto('/repo/create');
  await page.fill('input[name="repo_name"]', 'test-repo');
  await page.click('button[type="submit"]');
  
  await expect(page).toHaveURL(/\/.*\/test-repo/);
});
```

## Performance Optimization

### Code Splitting

```typescript theme={null}
// Lazy load large components
const MarkdownEditor = () => import('./components/MarkdownEditor.vue');
```

### Asset Optimization

* **Image optimization**: WebP format with fallbacks
* **Icon sprites**: SVG sprite sheets
* **CSS minification**: Production builds
* **Tree shaking**: Remove unused code

## Best Practices

<CardGroup cols={2}>
  <Card title="Use TypeScript" icon="check">
    Always type your components and functions
  </Card>

  <Card title="Scoped Styles" icon="check">
    Use `<style scoped>` to prevent style leakage
  </Card>

  <Card title="Composition API" icon="check">
    Prefer Composition API over Options API
  </Card>

  <Card title="Accessibility" icon="universal-access">
    Use semantic HTML and ARIA attributes
  </Card>
</CardGroup>

## See Also

<CardGroup cols={2}>
  <Card title="Architecture" href="/development/architecture">
    Overall architecture overview
  </Card>

  <Card title="Building" href="/development/building">
    Build Gitea from source
  </Card>

  <Card title="Contributing" href="/development/contributing">
    Contribution guidelines
  </Card>
</CardGroup>
