Optymalizacja wydajności aplikacji z Cursorem
Optymalizacja wydajności aplikacji z Cursorem to pętla zaczynająca się od pomiaru: bazowe wyniki z prawdziwych narzędzi, ranking wąskich gardeł ułożony przez agenta na podstawie tych danych, jedna poprawka na zmianę (zapytania N+1, waga bundla albo re-rendery) i akceptacja dopiero wtedy, gdy powtórzony pomiar, niezmienione testy i budżet w CI potwierdzają zysk.
Ta strona jest dla programisty, który odpowiada za wolną funkcję, i dla tech leada, który ma zatwierdzić poprawkę. Dashboard rozliczeń w twoim SaaS ładuje się 6 sekund, endpoint zużycia ma p95 równe 2,4 sekundy, a CPU bazy skacze za każdym razem, gdy ktoś otwiera zakładkę Usage analytics. Agent chętnie „zoptymalizuje” wszystko, co mu wskażesz, więc ryzykiem nie jest brak zmian. Ryzykiem jest wiarygodnie wyglądający diff, który niczego nie przyspiesza albo przyspiesza i psuje wynik.
Zakres: ta strona dotyczy kodu twojej aplikacji. Jeśli wolny jest sam Cursor (indeksowanie, rozszerzenia, pamięć), zobacz strojenie Cursora pod duże projekty. Problemy infrastruktury produkcyjnej, takie jak wyczerpana pula połączeń czy „skaczący” autoscaler, opisuje optymalizacja wydajności na produkcji.
Co daje pętla optymalizacji zaczynająca się od pomiaru
Dział zatytułowany „Co daje pętla optymalizacji zaczynająca się od pomiaru”- Folder
perf/baseline/z wynikami poleceń, które uruchamiasz ty, więc żadna liczba w PR nie pochodzi z wyobraźni modelu - Dwa prompty tylko do odczytu, które zamieniają surowe czasy, logi zapytań i trace’y w ranking wąskich gardeł
- Prompty naprawcze dla zapytań N+1, wagi bundla i re-renderów React, każdy z własnym testem-strażnikiem
- Budżet rozmiaru bundla w CI i test liczby zapytań, które nie pozwalają zyskowi po cichu zniknąć
- Tabelę przed/po, którą recenzent zatwierdzi bez czytania każdej zmienionej linii
Jakim narzędziem mierzyć który objaw?
Dział zatytułowany „Jakim narzędziem mierzyć który objaw?”Wybierz pomiar według objawu, zanim napiszesz prompt. Agent dobrze czyta duże wyniki, ale nie jest profilerem, a liczba, którą „raportuje” bez pliku za nią stojącego, jest zgadywaniem.
| Objaw | Czym mierzyć | Co czyta agent |
|---|---|---|
| Wolny endpoint API, wysokie p95 | Test obciążeniowy autocannon 8.0.0 z wyjściem JSON (-j) | perf/baseline/*-load.json |
| Skoki CPU bazy, wiele zapytań na żądanie | Logger zapytań ORM (w Drizzle logger: true) plus licznik zapytań na żądanie | Log zapytań pogrupowany po ID żądania |
| Wolne pierwsze ładowanie, dużo JavaScriptu | npx next experimental-analyze (Next.js 16.1+, tylko Turbopack) albo @next/bundle-analyzer 16.3.6 (buildy webpack) | Raport analizatora i rozmiary chunków |
| Słabe LCP, INP lub CLS w przeglądarce | CLI Lighthouse 13.5.0 albo trace przez serwer MCP Chrome DevTools | JSON z Lighthouse, wnioski z trace’a |
| Interfejs przycina się po załadowaniu | Profiler w React DevTools, eksport przez Save profile | Wyeksportowany JSON profilu |
Wersje sprawdzono w npm 26 września 2026 r. Pakiet web-vitals (6.2.2) eksportuje onCLS, onFCP, onINP, onLCP i onTTFB, a nie eksportuje onFID, więc wygenerowany kod wywołujący onFID jest nieaktualny.
Jak zebrać pomiar bazowy, którego agent nie wymyśli
Dział zatytułowany „Jak zebrać pomiar bazowy, którego agent nie wymyśli”Uruchom pomiary sam (albo pozwól agentowi uruchomić je w terminalu) i zapisz surowe wyniki do plików. Każde późniejsze porównanie czyta te pliki, nigdy historię czatu. Mierz build produkcyjny: na serwerze deweloperskim czasy zależą głównie od kompilacji na żądanie.
# Terminal, na buildzie produkcyjnym (npm run build && npm run start)mkdir -p perf/baselinenpx autocannon -c 20 -d 30 -j "http://localhost:3000/api/billing/usage?month=2026-09" \ > perf/baseline/usage-load.jsonnpx lighthouse http://localhost:3000/billing --only-categories=performance \ --preset=desktop --output=json --output-path=perf/baseline/billing-lighthouse.json --quietOprócz testu obciążeniowego potrzebujesz widoku pojedynczego żądania: ile zapytań wykonuje i ile one trwają. Ta instrumentacja to pierwsza rzecz, o którą prosisz agenta.
Gdy instrumentacja jest gotowa, zbierz log żądań. Blok pomiaru bazowego powyżej uruchomiłeś, zanim istniała, więc powtórz test obciążeniowy z włączonym logowaniem:
# Terminal 1: serwer produkcyjny z logowaniem żądań; zostają tylko linie JSONPERF_LOGGING=1 npm run start --silent | grep --line-buffered '^{' > perf/baseline/requests.jsonl
# Terminal 2: powtórz bazowy test obciążeniowynpx autocannon -c 20 -d 30 -j "http://localhost:3000/api/billing/usage?month=2026-09" \ > perf/baseline/usage-load.json--silent usuwa baner skryptu npm, a grep odfiltrowuje linie startowe, które wypisuje next start, więc jq czyta później czysty JSON Lines, zamiast wyłożyć się na pierwszej linii, która nie jest JSON-em. Zbierz w perf/baseline/requests.jsonl co najmniej 100 żądań do wolnych endpointów. autocannon podaje p97,5 i p99, a nie p95; p95 pochodzi z requests.jsonl. Zacommituj folder perf/baseline/ na gałęzi: to dowód, z którym porówna wyniki recenzent.
Przy pracy nad przeglądarką serwer MCP Chrome DevTools (pakiet npm chrome-devtools-mcp 1.10.1, rozwijany przez zespół Chrome DevTools) pozwala agentowi nagrać trace wydajności (performance_start_trace / performance_stop_trace) i odczytać wnioski z niego (performance_analyze_insight). Jego narzędzie lighthouse_audit pomija kategorię wydajności (sprawdzone w 1.10.1), więc wyniki LCP i TBT nadal bierz z uruchomienia CLI Lighthouse. Dodaj go do .cursor/mcp.json (kształt pochodzi z README producenta; zobacz konfigurację MCP w Cursorze):
{ "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest", "--isolated", "--no-usage-statistics", "--no-performance-crux"] } }}Serwer wysyła statystyki użycia do Google, dopóki nie podasz --no-usage-statistics, a jego narzędzia wydajnościowe mogą wysyłać adresy z trace’ów do API CrUX, dopóki nie dodasz --no-performance-crux. Przy wewnętrznych adresach dodaj obie flagi. Każdy podłączony serwer dokłada też definicje swoich narzędzi do kontekstu agenta; zobacz zarządzanie tokenami w Cursorze.
Jak znaleźć prawdziwe wąskie gardło w danych z profilowania?
Dział zatytułowany „Jak znaleźć prawdziwe wąskie gardło w danych z profilowania?”Poproś o analizę w Plan Mode, który tworzy plan, zanim powstanie jakikolwiek kod. Celem tego kroku jest ranking z testem potwierdzającym dla każdej pozycji, a nie poprawka.
Sprawdź sam dwie liczby z odpowiedzi agenta w surowym pliku, na przykład p95 jednego endpointu:
# Terminal: p95 duration_ms jednego endpointu (metoda najbliższej rangi)jq -s '[.[] | select(.path=="/api/billing/usage").duration_ms] | sort | .[((length*0.95)|ceil) - 1]' perf/baseline/requests.jsonlJeśli się nie zgadzają, agent próbkował albo źle odczytał log i ranking nie jest wiarygodny, dopóki nie przeliczy go od nowa. Gdy pozycja rankingu wskazuje konkretną przyczynę, potwierdź ją przed poprawką: tezę o N+1 potwierdzają linie logu jednego żądania, tezę o bundlu raport analizatora.
Jak naprawić zapytania N+1, nie zmieniając odpowiedzi
Dział zatytułowany „Jak naprawić zapytania N+1, nie zmieniając odpowiedzi”Zapytanie N+1 ładuje wiersze nadrzędne, a potem wykonuje jedno zapytanie na każdy wiersz. W scenariuszu rozliczeń endpoint zużycia wykonuje jedno zapytanie o organizacje enterprise, a potem jedno SUM(amount) na organizację: 341 zapytań dla 340 organizacji.
Przed poprawką zamroź zachowanie. Snapshot obecnej odpowiedzi JSON i test liczący zapytania na żądanie zamieniają „diff wygląda dobrze” w dwie asercje, które nie przejdą, jeśli dobrze nie jest.
Linia z LEFT JOIN nie jest ozdobą. Najczęstszy błędny zapis tego wzorca to złączenie wewnętrzne, które gubi każdą organizację bez wierszy zużycia w danym miesiącu. Na demie odpowiedź wygląda poprawnie, a na produkcji po cichu traci wiersze. Wyłapuje to właśnie snapshot z organizacją o zerowym zużyciu.
Jak zmniejszyć bundle i utrzymać ten wynik
Dział zatytułowany „Jak zmniejszyć bundle i utrzymać ten wynik”Zacznij od analizatora, a nie od listy bibliotek, które podejrzewasz.
# Terminal: interaktywny analizator bundla Turbopacka; nie tworzy buildu aplikacji (Next.js 16.1+, tylko Turbopack)npx next experimental-analyze# Terminal: wymaga @next/bundle-analyzer podpiętego w next.config.js# Next.js 16 domyślnie buduje Turbopackiem; ten analizator działa tylko z --webpackANALYZE=true npx next build --webpack # zapisuje client.html, edge.html i nodejs.html w .next/analyze/Jeśli wolisz analizę z prawdziwego buildu produkcyjnego, next build --experimental-analyze uruchamia ten sam analizator w trakcie buildu (tylko Turbopack, sprawdzone w next 16.3.6).
Wklej do promptu największe moduły z raportu. Liczby poniżej dotyczą dashboardu rozliczeń; zastąp je swoimi.
Budżet size-limit (14.1.0, domyślnie rozmiary po kompresji brotli) to mechanizm, który utrzymuje zysk. Limit ustawiasz tuż powyżej osiągniętego rozmiaru, a nie poniżej: limit niższy niż obecna suma wywraca CI już na commicie, który go dodaje. Bez niego następna gałąź z nową funkcją przywróci ciężki import i nikt tego nie zauważy, dopóki wyniki Lighthouse nie zaczną spadać.
Jak zatrzymać zbędne re-rendery React
Dział zatytułowany „Jak zatrzymać zbędne re-rendery React”Gdy bundle ma rozsądny rozmiar, a interfejs nadal się przycina, nagraj interakcję w Profilerze React DevTools, zapisz profil i przekaż agentowi plik oraz zaangażowane komponenty.
Potem nagraj tę samą interakcję jeszcze raz i porównaj oba profile. Poprawka, która dodaje React.memo do komponentów od początku tanich w renderowaniu, to narzut, a nie optymalizacja.
Jak udowodnić zysk bez czytania każdej linii
Dział zatytułowany „Jak udowodnić zysk bez czytania każdej linii”Uruchom dokładnie te same polecenia co przy pomiarze bazowym, zapisując wyniki do perf/after/, zbierz perf/after/requests.jsonl tak samo jak przy pomiarze bazowym (co najmniej 100 żądań do tych samych endpointów), puść pełny zestaw testów i każ agentowi porównać pliki, a nie wspomnienia.
Wklej tę tabelę do opisu PR. Recenzent sprawdza wtedy dowody zamiast czytać diff linia po linii:
| Dowód | Co udowadnia | Kto sprawdza |
|---|---|---|
perf/baseline/ i perf/after/ z tych samych poleceń na buildzie produkcyjnym | Zysk jest prawdziwy i dwa razy zmierzony tak samo | Tech lead, w PR |
| Niezmieniony test snapshotu odpowiedzi | Szybszy kod zwraca te same dane | CI |
| Test liczby zapytań (najwyżej 2 na żądanie) | N+1 nie wróci niezauważony | CI |
Budżet size-limit | Bundle zostaje poniżej nowego limitu | CI |
| Wiersze regresji w tabeli | Żadna inna metryka nie zapłaciła za tę poprawę | Tech lead |
| Recenzja PR przez Bugbota | Drugi recenzent szuka w zmianie błędów i problemów bezpieczeństwa | Recenzent Cursora, potem autor |
Autor odpowiada za liczby; tech lead zatwierdza tabelę i zielone bramki, a kod czyta tylko tam, gdzie tabela pokazuje regresję albo zmieniono test. To podejście dowodów zamiast diffów zastosowane do wydajności. Przy cyklicznych kontrolach Automations w Cursorze mogą uruchomić Cloud Agenta ze zdarzenia w Sentry lub PagerDuty, więc alert o opóźnieniach przychodzi już z tą samą analizą pomiaru bazowego i rankingu.
Kiedy optymalizacja wydajności z agentem idzie źle
Dział zatytułowany „Kiedy optymalizacja wydajności z agentem idzie źle”Agent optymalizuje coś, co nie jest wąskim gardłem. Prosisz o szybszy endpoint, a dostajesz 2 ms oszczędności w funkcji wewnątrz żądania trwającego 2000 ms. Jak z tego wyjść: wróć do promptu z rankingiem i wymagaj, żeby każda poprawka wskazywała wiersz pomiaru bazowego, którego dotyczy. Poprawka bez takiego wiersza nie jest mergowana.
Liczby w PR pochodzą z czatu, a nie z pliku. Agent „uruchomił benchmark” na serwerze deweloperskim albo streścił przebieg, który przekroczył limit czasu. Jak z tego wyjść: przyjmuj tylko liczby obecne w perf/after/, wygenerowane tymi samymi poleceniami co pomiar bazowy, na buildzie produkcyjnym. Z tego powodu prompt porównujący odmawia uzupełniania brakujących wartości.
Szybsze zapytanie zwraca inne dane. Złączenie wewnętrzne gubi puste grupy, nowy cache serwuje sumy innego klienta albo zmiana zaokrąglania przesuwa grosze. Jak z tego wyjść: test snapshotu nie przechodzi; wycofaj handler i poproś o ponowne przepisanie, cytując przypadek, który zawiódł. Nigdy nie pozwalaj agentowi aktualizować snapshotu w tej samej zmianie, która przyspiesza kod.
Memoizacja wszędzie. React.memo, useMemo i useCallback kosztują pamięć i czas porównań. Jak z tego wyjść: usuń każde memo, którego nie uzasadnia profil, i sprofiluj ponownie; jeśli czas interakcji się nie zmienił, memo były szumem.
Nowy indeks spowalnia zapis. Każdy indeks dokłada pracy do każdego INSERT-u w tabeli o dużej liczbie zapisów, takiej jak zdarzenia zużycia. Jak z tego wyjść: przed merge’em porównaj opóźnienie zapisów w teście obciążeniowym z przewagą zapisów; jeśli jest gorzej, spróbuj węższego indeksu złożonego albo tabeli z wstępnie zagregowanymi danymi.
Dzielenie kodu spowalnia stronę. Leniwie ładowany chunk, który jest mały i potrzebny na większości stron, dokłada żądanie przy znikomej oszczędności. Jak z tego wyjść: wydzielaj tylko chunki duże i używane na nielicznych trasach, a w perf/after/ sprawdzaj LCP, nie tylko rozmiar bundla.
Szybka wersja jest nie do utrzymania. Ręcznie pisane zapytanie z zagnieżdżonymi CTE bije ORM o 30 ms i nikt nie potrafi go zmienić. Jak z tego wyjść: domyślnie zostań przy kodzie ORM, do surowego SQL schodź tylko na gorącej ścieżce ze zmierzonym wymaganiem i wymagaj komentarza z linkiem do benchmarku, który to uzasadnił.
Dokąd dalej z pracą nad wydajnością
Dział zatytułowany „Dokąd dalej z pracą nad wydajnością”Ta sama pętla „najpierw pomiar” opisana dla innych agentów: analiza wydajności aplikacji z Claude Code i optymalizacja wydajności z Codexem.