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}/token/
<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.