ecamp / ecamp/ecamp3

Performance issue loading Mobile/Desktop

Open
#10,396 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
156
Forks
72
Avg merge
12h 43m
Merged PRs (30d)
203

Description

Slow loading when opening for the first time.

Especially on mobile devices, where the cache is cleared frequently, the loading time is much longer.

During the storage season, I’ve heard several times that it’s “extremely” slow—especially when the connection is poor.

I tested it at home with
Wi-Fi:
- Download speed: 216 Mbps
- Upload speed: 46.6 Mbps

When opening the storage, the loading time is about 6 seconds.

It’s not that it doesn’t work, but there’s room for improvement.

on homedesktop with following Envoirement
LAN:
- Download speed: 218.29 Mbps
- Upload speed: 47.75 Mbps

Image

Selected a Camp

Image
## 🤖 AI Analysis (Performance Trace)

Based on a DevTools performance trace of the dashboard load (`/camps/{id}/dashboard`). Total duration Navigation Start → LCP: **~5.9s**.

### 1. API calls run too sequentially instead of in parallel ⚠️ (Main issue)

| Request | Start | Duration |
|---|---|---|
| `GET /api/camps/{id}` | 8,507ms | 992ms |
| `GET /api/days?period.camp=...` | 9,595ms | 399ms |
| `GET /api/camps/{id}/activities` | 9,596ms | **3,018ms** |
| `GET /api/camps/{id}/categories` | 12,167ms | 898ms |
| `GET /api/camps/{id}/activity_progress_labels` | 12,611ms | 872ms |

- `/activities` alone takes **3s** (response size 159KB) – the single biggest blocker.
- `categories` and `activity_progress_labels` only start **after** `activities` has (almost) finished, instead of firing alongside the other calls → suggests nested components that only trigger their own fetch once the activities data has arrived.

*Note: all data must be displayed in full — pagination/filtering the dataset is not an option.*

**Suggestion:**
- [ ] Load all dashboard data centrally (store/composable) via `Promise.all()` on route change instead of sequentially per child component → all requests start at the same time instead of staggered
- [ ] Backend: check the `/activities` endpoint for N+1 queries, missing DB indexes, and serialization overhead (data volume stays the same, but query efficiency can be improved)
- [ ] Check response compression (is Brotli/Gzip enabled? payload is 159KB uncompressed)
- [ ] Evaluate server-side caching for parts that rarely change (e.g. `categories`, `activity_progress_labels`) to speed up repeat loads

### 2. Render-blocking CSS (~1MB) 🎨

| File | Size |
|---|---|
| `plugins-*.css` | 605 KB |
| `index-*.css` | 347 KB |

Un-tree-shaken Vuetify CSS is loaded in full before the first paint.

**Suggestion:**
- [ ] Use `vite-plugin-vuetify` for automatic tree-shaking of component styles
- [ ] Remove unused utility classes via PurgeCSS

### 3. Long main-thread tasks during rendering 🐢

Right after the activities data arrives: 3 long tasks of 708ms / 439ms / 406ms (~1.5s of JS blocking), likely due to deep Vue reactivity on a large array.

**Suggestion:**
- [ ] Virtualize the schedule list (`vue-virtual-scroller`) – still renders **all** entries, but only visible ones as real DOM nodes → same data volume, lower rendering cost
- [ ] Bind large read-only API responses with `shallowRef`/`markRaw` to avoid unnecessary deep reactivity on the full dataset

---

**Priority by impact:**
1. Parallelize API calls + optimize `/activities` on the backend (query/index/compression) (~2-3s gain)
2. Reduce CSS bundle size
3. Virtualize list rendering (full dataset remains intact)

*Note: the trace also includes overhead from 2 browser extensions (Adobe Acrobat, one other) – for exact measurements, re-test in Incognito mode is recommended.*

------ END of ISSUE -----

## 🤖 KI-Analyse (Performance Trace) (Deutsch)

Basierend auf einem DevTools Performance-Trace des Dashboard-Ladevorgangs (`/camps/{id}/dashboard`). Gesamtdauer Navigation Start → LCP: **~5.9s**.

### 1. API-Calls laufen zu sequentiell statt parallel ⚠️ (Hauptproblem)

| Request | Start | Dauer |
|---|---|---|
| `GET /api/camps/{id}` | 8'507ms | 992ms |
| `GET /api/days?period.camp=...` | 9'595ms | 399ms |
| `GET /api/camps/{id}/activities` | 9'596ms | **3'018ms** |
| `GET /api/camps/{id}/categories` | 12'167ms | 898ms |
| `GET /api/camps/{id}/activity_progress_labels` | 12'611ms | 872ms |

- `/activities` allein braucht **3s** (Response-Grösse 159KB) – grösster Einzel-Blocker.
- `categories` und `activity_progress_labels` starten erst, **nachdem** `activities` (fast) abgeschlossen ist, statt gleichzeitig mit den übrigen Calls zu feuern → deutet auf verschachtelte Komponenten, die erst nach Erhalt der activities-Daten ihren eigenen Fetch auslösen.

*Hinweis: alle Daten müssen vollständig angezeigt werden, Pagination/Filterung des Datensatzes ist keine Option.*

**Vorschlag:**
- [ ] Alle Dashboard-Daten zentral (Store/Composable) per `Promise.all()` beim Routenwechsel laden statt pro Kind-Komponente sequentiell → Requests starten alle gleichzeitig statt gestaffelt
- [ ] Backend: `/activities`-Endpoint auf N+1-Queries, fehlende DB-Indizes und Serialisierungs-Overhead prüfen (Datenmenge bleibt gleich, aber Query-Effizienz erhöhen)
- [ ] Response-Kompression prüfen (Brotli/Gzip aktiv? Payload ist 159KB unkomprimiert)
- [ ] Serverseitiges Caching für selten ändernde Anteile (z.B. `categories`, `activity_progress_labels`) evaluieren, um wiederholte Ladezeiten zu verkürzen

### 2. Render-blockierendes CSS (~1MB) 🎨

| Datei | Grösse |
|---|---|
| `plugins-*.css` | 605 KB |
| `index-*.css` | 347 KB |

Ungetreeshaktes Vuetify-CSS wird komplett vor dem ersten Paint geladen.

**Vorschlag:**
- [ ] `vite-plugin-vuetify` für automatisches Tree-Shaking der Component-Styles einsetzen
- [ ] Ungenutzte Utility-Klassen per PurgeCSS entfernen

### 3. Lange Main-Thread-Tasks beim Rendern 🐢

Nach Erhalt der activities-Daten: 3 Long Tasks à 708ms / 439ms / 406ms (~1.5s JS-Blocking), vermutlich durch tiefe Vue-Reaktivität auf grossem Array.

**Vorschlag:**
- [ ] Virtualisierung für Schedule-Liste (`vue-virtual-scroller`) – rendert weiterhin **alle** Einträge, aber nur die sichtbaren als echte DOM-Nodes → gleicher Datenumfang, weniger Rendering-Kosten
- [ ] Grosse Read-only API-Responses mit `shallowRef`/`markRaw` einbinden, um unnötige tiefe Reaktivität auf dem kompletten Datensatz zu vermeiden

---

**Priorisierung nach Wirkung:**
1. API-Calls parallelisieren + `/activities` backend-seitig optimieren (Query/Index/Kompression) (~2-3s Gewinn)
2. CSS-Bundle-Grösse reduzieren
3. Listen-Rendering virtualisieren (Datenumfang bleibt vollständig erhalten)

*Hinweis: Trace enthält auch Overhead durch 2 Browser-Extensions (Adobe Acrobat, eine weitere) – für exakte Messwerte Re-Test im Inkognito-Modus empfohlen.*

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the /camps/{id}/dashboard route and inspecting the listed API requests, dashboard components, and generated plugins-*.css and index-*.css bundles. Compare the trace with the suggested centralized loading, /activities backend checks, and rendering changes. Done means the dashboard still displays all data while requests and rendering complete faster than the reported ~5.9 seconds.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, php, vite
Domain
api, backend, frontend, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.