Przejdź do głównej zawartości

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

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.

ObjawCzym mierzyćCo czyta agent
Wolny endpoint API, wysokie p95Test obciążeniowy autocannon 8.0.0 z wyjściem JSON (-j)perf/baseline/*-load.json
Skoki CPU bazy, wiele zapytań na żądanieLogger zapytań ORM (w Drizzle logger: true) plus licznik zapytań na żądanieLog zapytań pogrupowany po ID żądania
Wolne pierwsze ładowanie, dużo JavaScriptunpx 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ądarceCLI Lighthouse 13.5.0 albo trace przez serwer MCP Chrome DevToolsJSON z Lighthouse, wnioski z trace’a
Interfejs przycina się po załadowaniuProfiler w React DevTools, eksport przez Save profileWyeksportowany 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.

Okno terminala
# Terminal, na buildzie produkcyjnym (npm run build && npm run start)
mkdir -p perf/baseline
npx autocannon -c 20 -d 30 -j "http://localhost:3000/api/billing/usage?month=2026-09" \
> perf/baseline/usage-load.json
npx lighthouse http://localhost:3000/billing --only-categories=performance \
--preset=desktop --output=json --output-path=perf/baseline/billing-lighthouse.json --quiet

Opró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:

Okno terminala
# Terminal 1: serwer produkcyjny z logowaniem żądań; zostają tylko linie JSON
PERF_LOGGING=1 npm run start --silent | grep --line-buffered '^{' > perf/baseline/requests.jsonl
# Terminal 2: powtórz bazowy test obciążeniowy
npx 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:

Okno terminala
# 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.jsonl

Jeś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.

Zacznij od analizatora, a nie od listy bibliotek, które podejrzewasz.

Okno terminala
# Terminal: interaktywny analizator bundla Turbopacka; nie tworzy buildu aplikacji (Next.js 16.1+, tylko Turbopack)
npx next experimental-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ć.

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.

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ódCo udowadniaKto sprawdza
perf/baseline/ i perf/after/ z tych samych poleceń na buildzie produkcyjnymZysk jest prawdziwy i dwa razy zmierzony tak samoTech lead, w PR
Niezmieniony test snapshotu odpowiedziSzybszy kod zwraca te same daneCI
Test liczby zapytań (najwyżej 2 na żądanie)N+1 nie wróci niezauważonyCI
Budżet size-limitBundle zostaje poniżej nowego limituCI
Wiersze regresji w tabeliŻadna inna metryka nie zapłaciła za tę poprawęTech lead
Recenzja PR przez BugbotaDrugi recenzent szuka w zmianie błędów i problemów bezpieczeństwaRecenzent 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.

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ł.

Ta sama pętla „najpierw pomiar” opisana dla innych agentów: analiza wydajności aplikacji z Claude Code i optymalizacja wydajności z Codexem.