Functional tests tell you whether a feature works. Performance tests tell you whether it still works when real users show up. Too often, performance testing happens once before a big launch—if at all. The strategies below make it a continuous, routine part of delivery.
Know which question you are asking
Different performance tests answer different questions. Pick the type before you pick the tool.
- Load test — Does the system meet its targets under expected traffic?
- Stress test — Where does it break, and does it fail gracefully?
- Spike test — Can it absorb sudden bursts, like a marketing email or a viral post?
- Soak test — Does performance degrade over hours (memory leaks, connection exhaustion)?
- Front-end performance — How fast does the page feel to a real user on a real device?
Choosing the right tool
k6 — code-first load testing
k6 scripts are written in JavaScript, live in the same repository as your code, and run cleanly in CI. Thresholds turn performance goals into pass/fail criteria, so a slow build fails just like a broken one.
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 50 }, // ramp up to 50 virtual users
{ duration: "5m", target: 50 }, // hold steady load
{ duration: "1m", target: 0 }, // ramp down
],
thresholds: {
http_req_failed: ["rate<0.01"], // less than 1% errors
http_req_duration: ["p(95)<800"], // 95% of requests under 800ms
},
};
export default function () {
const res = http.get(`${__ENV.BASE_URL}/library`);
check(res, { "status is 200": (r) => r.status === 200 });
sleep(1);
}Run it with k6 run -e BASE_URL=https://staging.example.com load-test.js. If a threshold is breached, k6 exits with a non-zero code and the pipeline fails.
JMeter — protocol breadth and complex scenarios
JMeter shines when you need many protocols, complex correlation, or a GUI to design test plans with stakeholders. Design in the GUI, but always execute in non-GUI mode for accurate results:
jmeter -n -t checkout-plan.jmx -l results.jtl -e -o report/Lighthouse — what users actually experience
Back-end speed is only half the story. Lighthouse audits front-end performance and reports Google's Core Web Vitals, the metrics that reflect real user experience:
- Largest Contentful Paint (LCP) — loading speed. Good: 2.5 seconds or less.
- Interaction to Next Paint (INP) — responsiveness. Good: 200 milliseconds or less.
- Cumulative Layout Shift (CLS) — visual stability. Good: 0.1 or less.
Lighthouse CI can enforce budgets on every build:
{
"ci": {
"collect": {
"url": ["https://staging.example.com/"],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["warn", { "maxNumericValue": 200 }]
}
}
}
}Read the results like an engineer
- Use percentiles, not averages. An average of 300ms can hide a p99 of 5 seconds—and those slow requests are the ones users remember.
- Establish a baseline. A number means little until you can compare it to the last release.
- Correlate with server metrics. CPU, memory, database connections, and slow queries explain why latency rose.
- Test in a production-like environment. Results from an undersized staging server can mislead in either direction.
Make performance testing continuous
- 1Run Lighthouse CI on every pull request for key pages.
- 2Run a short k6 smoke load test on every deployment to staging.
- 3Schedule full load, stress, and soak tests nightly or weekly.
- 4Alert the team automatically when thresholds are breached.
Performance is a feature. Test it like one—continuously, with clear thresholds, and with results everyone can see.