Frontend Development¶
The Meet frontend is a TypeScript/React SPA built with Vite.
Tech stack¶
| Component | Technology |
|---|---|
| Framework | React 18 |
| Language | TypeScript |
| Build tool | Vite |
| WebRTC | @livekit/components-react |
| Accessible UI | React Aria (Adobe) |
| State management | Zustand |
| i18n | i18next |
| Testing | Vitest + React Testing Library |
| Linting | ESLint + Prettier |
Running in dev mode¶
# Start backend services
make run-backend
# Install and start the frontend dev server
make frontend-development-install
make run-frontend-development
# Or directly
cd src/frontend
npm install
npm run dev
Dev server runs at http://localhost:5173 with hot module replacement.
Project structure¶
src/frontend/src/
├── api/ # Typed fetch wrappers for backend endpoints
├── components/ # Shared/reusable components
├── features/
│ ├── conference/ # In-meeting UI
│ │ ├── controls/ # Mic, camera, screen share, reactions
│ │ ├── layout/ # Video grid, speaker view
│ │ ├── chat/ # Chat panel
│ │ └── recording/# Recording / transcription panels
│ ├── home/ # Home page, room creation
│ └── settings/ # Settings panels
├── hooks/ # Custom React hooks
├── stores/ # Zustand state stores
├── i18n/ # Translation files (en.json, fr.json, …)
└── App.tsx # Root component + routing
LiveKit connection¶
import { LiveKitRoom, VideoConference } from '@livekit/components-react';
// Token and URL come from GET /api/v1.0/rooms/{id}/ → response.livekit.token / .url
<LiveKitRoom token={token} serverUrl={url} connect>
<VideoConference />
</LiveKitRoom>
State management¶
import { create } from 'zustand';
interface ConferenceStore {
isRecording: boolean;
setIsRecording: (v: boolean) => void;
}
export const useConferenceStore = create<ConferenceStore>((set) => ({
isRecording: false,
setIsRecording: (v) => set({ isRecording: v }),
}));
Accessibility¶
Meet uses React Aria for accessible component primitives. Rules:
- All interactive elements must have an accessible name (
aria-labelor visible text) - State changes (mute/unmute, recording) must be announced via ARIA live regions
- Focus management: opening dialogs moves focus in; closing returns focus to the trigger
- Test every new interactive element with keyboard navigation
import { Button } from 'react-aria-components';
<Button
aria-label={isMuted ? "Unmute microphone" : "Mute microphone"}
aria-pressed={isMuted}
onPress={toggleMute}
>
{isMuted ? <MicOffIcon /> : <MicIcon />}
</Button>
Internationalization¶
- Add the key to
en.jsonandfr.json(minimum) - Use in components:
Other languages are managed via Crowdin.
Running tests¶
Linting and formatting¶
Build-time environment variables¶
Set as Docker build arguments (VITE_ prefix):
| Variable | Purpose |
|---|---|
VITE_API_BASE_URL |
Not used in the published image: API calls use relative URLs |
VITE_APP_TITLE |
App title shown in the browser tab |
In the published lasuite/meet-frontend image, the frontend makes API calls using relative URLs (/api/v1.0/...). This means it works correctly as long as the SPA and the API are served from the same origin, which is exactly what the routing nginx in the frontend container provides.