「ページが遅い気がするけど、どこから手を付ければ?」「console.timeでザックリ測ってるけど、本当に正確?」「Web Vitalsを計測してSentryやGA4に飛ばす方法が分からない」——JavaScriptのパフォーマンス計測は、ツールが多すぎて初心者がつまずきやすい領域です。
本記事では、ブラウザのPerformance API、Web Vitals、Benchmark.js / tinybench / mitata、Chrome DevTools Profiler、Lighthouse CI、Node.js perf_hooks / clinic.js、さらにSentry / DataDog / GA4へのRUM送信まで、現場で実際に使える計測手法を40本以上のコピペ可能なJS/TSコードで解説します。「測れない最適化は祈り」をやめて、数値で語れるエンジニアになりましょう。
- 1. JavaScriptパフォーマンス計測の全体像
- 2. Performance API — 高精度タイマーとUser Timing
- 3. Web Vitals — ユーザー体感の3指標を測る
- 4. PerformanceObserver — ブラウザ内部イベントを購読する
- 5. Memory計測 — リーク検出とヒープ監視
- 6. マイクロベンチマーク — 関数単位の速度比較
- 7. Chrome DevTools Performance Profiler
- 8. Lighthouse / Lighthouse CI
- 9. RUM(Real User Monitoring)— 実ユーザー計測
- 10. Node.js のパフォーマンス計測
- 11. OpenTelemetry — 分散トレーシング
- 12. CI/CDに組み込む実践パターン
- 13. 計測データを「行動」につなげるためのチェックリスト
- 14. まとめ — 「測れない最適化」を卒業しよう
- 15. パフォーマンス学習をキャリアに変える
1. JavaScriptパフォーマンス計測の全体像
1-1. 「計測しない最適化」は害悪である
パフォーマンスチューニングで最も多い失敗は「体感で速くなった気がする」だけで終わることです。実際には変わっていない、あるいは別の場所が遅くなっているケースが半分以上を占めます。まずは計測→ボトルネック特定→改善→再計測のサイクルを徹底しましょう。
1-2. 計測の3レイヤー(マイクロ・ページ・フィールド)
- マイクロベンチマーク: 関数単位の速度比較(Benchmark.js / tinybench / mitata)
- ページパフォーマンス: 1ページの読み込み・描画・操作応答(Performance API / Lighthouse)
- フィールドデータ: 実ユーザー環境の計測(web-vitals / Sentry / DataDog / CrUX)
3レイヤーを混同するとデータの意味を読み違えます。「Lighthouseで90点なのに実ユーザー体感が悪い」という典型例は、ラボとフィールドのギャップです。
1-3. console.timeから卒業しよう
// 簡易だが粒度が荒くstackable性も弱い
console.time("hoge");
doSomething();
console.timeEnd("hoge"); // hoge: 12.345ms
console.time自体は便利ですが、ミリ秒未満の精度・ネスト・複数計測には不向きです。次節からはより高精度な手段に移ります。
2. Performance API — 高精度タイマーとUser Timing
2-1. performance.now() の基本
// Date.now()よりはるかに高精度(0.005ms単位)で単調増加
const t0 = performance.now();
for (let i = 0; i < 1_000_000; i++) {
Math.sqrt(i);
}
const t1 = performance.now();
console.log(`elapsed: ${(t1 - t0).toFixed(3)} ms`);
2-2. performance.mark() と performance.measure()
// 名前付きマークで時系列を残せる(DevToolsのPerformanceパネルにも出る)
performance.mark("fetch-start");
await fetch("/api/users");
performance.mark("fetch-end");
performance.measure("fetch-duration", "fetch-start", "fetch-end");
const entries = performance.getEntriesByName("fetch-duration");
console.log(entries[0].duration.toFixed(2), "ms");
2-3. measureUserAgentSpecificMemory(将来用)
// Chrome系で利用可能。crossOriginIsolated必須
if (performance.measureUserAgentSpecificMemory) {
const result = await performance.measureUserAgentSpecificMemory();
console.log("bytes:", result.bytes);
}
2-4. PerformanceObserverで非同期に拾う
// markされたエントリを後から束で受け取れる(同期処理を邪魔しない)
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.duration);
}
});
po.observe({ entryTypes: ["measure"] });
2-5. 関数デコレーター化して計測を自動化
// 任意の関数を「mark/measure付き」にラップするユーティリティ
function withTiming<T extends (...args: any[]) => any>(label: string, fn: T): T {
return ((...args: Parameters<T>) => {
performance.mark(`${label}-start`);
const ret = fn(...args);
if (ret instanceof Promise) {
return ret.finally(() => {
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
});
}
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
return ret;
}) as T;
}
const slowAdd = withTiming("slowAdd", (a: number, b: number) => {
for (let i = 0; i < 1e6; i++) {}
return a + b;
});
slowAdd(1, 2);
2-6. clearMarks / clearMeasuresで肥大化を防ぐ
// 長時間稼働ページでは溜まり続けるので定期的にクリアする
setInterval(() => {
performance.clearMarks();
performance.clearMeasures();
}, 60_000);
3. Web Vitals — ユーザー体感の3指標を測る
3-1. Core Web Vitalsの再整理(LCP / INP / CLS)
- LCP(Largest Contentful Paint): 主要コンテンツが描画されるまで。目標2.5秒以内
- INP(Interaction to Next Paint): 操作から次の描画まで。目標200ms以内
- CLS(Cumulative Layout Shift): 視覚的安定性。目標0.1未満
2024年にFIDがINPに置き換わったため、古い記事のFID計測コードは現在ほぼ無意味です。
3-2. web-vitalsライブラリの導入
npm install web-vitals
// すべてのCore Web Vitalsをまとめて取得
import { onLCP, onINP, onCLS, onTTFB, onFCP } from "web-vitals";
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
onTTFB(console.log);
onFCP(console.log);
3-3. attribution版で「どこが原因か」まで取る
import { onLCP } from "web-vitals/attribution";
onLCP((metric) => {
// どの要素がLCPを引き起こしたかが分かる
console.log("LCP element:", metric.attribution.element);
console.log("URL:", metric.attribution.url);
console.log("resource load delay:", metric.attribution.resourceLoadDelay);
});
3-4. INPの内訳を分解する
import { onINP } from "web-vitals/attribution";
onINP((metric) => {
const a = metric.attribution;
console.log({
eventType: a.interactionType,
eventTarget: a.interactionTargetElement,
inputDelay: a.inputDelay,
processingDuration: a.processingDuration,
presentationDelay: a.presentationDelay,
});
}, { reportAllChanges: true });
3-5. CLSの原因要素を特定
import { onCLS } from "web-vitals/attribution";
onCLS((metric) => {
for (const entry of metric.attribution.largestShiftSource ? [metric.attribution.largestShiftSource] : []) {
console.log("shift source:", entry.node);
}
});
4. PerformanceObserver — ブラウザ内部イベントを購読する
4-1. Navigation Timing(ページ全体の遷移指標)
const nav = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming;
console.log({
ttfb: nav.responseStart - nav.requestStart,
domContentLoaded: nav.domContentLoadedEventEnd - nav.startTime,
load: nav.loadEventEnd - nav.startTime,
transferSize: nav.transferSize,
encodedBodySize: nav.encodedBodySize,
});
4-2. Resource Timing(個別リソースの内訳)
// 遅いリソースTOP10を抽出
const slow = performance
.getEntriesByType("resource")
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
slow.forEach((r) => console.log(r.name, r.duration.toFixed(1), "ms"));
4-3. Long Tasks API(50ms超のメインスレッド占有)
// レンダリングを止める「長いタスク」を検出する
const lt = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn("long task:", entry.duration.toFixed(0), "ms", entry.attribution);
}
});
lt.observe({ type: "longtask", buffered: true });
4-4. Element Timing(任意要素の描画時刻)
<img src="/hero.jpg" elementtiming="hero-img" />
<h1 elementtiming="hero-heading">高速化を始めよう</h1>
new PerformanceObserver((list) => {
for (const entry of list.getEntries() as PerformanceElementTiming[]) {
console.log(entry.identifier, entry.renderTime.toFixed(0), "ms");
}
}).observe({ type: "element", buffered: true });
4-5. Event Timing(個別イベントの遅延)
new PerformanceObserver((list) => {
for (const entry of list.getEntries() as PerformanceEventTiming[]) {
if (entry.duration > 40) {
console.log("slow event:", entry.name, entry.duration.toFixed(0));
}
}
}).observe({ type: "event", buffered: true, durationThreshold: 16 });
4-6. Largest Contentful Paint 単体取得
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
console.log("LCP:", last.startTime.toFixed(0), "ms", (last as any).element);
}).observe({ type: "largest-contentful-paint", buffered: true });
4-7. paint タイプ(FP / FCP)
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime.toFixed(0), "ms");
}
}).observe({ type: "paint", buffered: true });
5. Memory計測 — リーク検出とヒープ監視
5-1. performance.memory(Chrome系のみ)
// 標準化前の非標準APIだが実運用デバッグでは便利
// @ts-ignore
const mem = performance.memory;
console.log({
usedJSHeapSize: mem.usedJSHeapSize,
totalJSHeapSize: mem.totalJSHeapSize,
jsHeapSizeLimit: mem.jsHeapSizeLimit,
});
5-2. メモリ増加を時系列ログにする
function logHeap(label: string) {
// @ts-ignore
const m = performance.memory;
if (!m) return;
console.log(label, (m.usedJSHeapSize / 1024 / 1024).toFixed(1), "MB");
}
setInterval(() => logHeap("tick"), 5000);
5-3. リークパターンを再現するスニペット
// イベントリスナーを外し忘れるとDOMがGCされず溜まり続ける
const leaked: HTMLElement[] = [];
function leak() {
const div = document.createElement("div");
document.body.appendChild(div);
div.addEventListener("click", () => console.log(div));
leaked.push(div); // GC対象にならない
}
setInterval(leak, 100);
5-4. WeakRef / FinalizationRegistryでGCを観察
const registry = new FinalizationRegistry((label) => {
console.log("GCされた:", label);
});
let obj: any = { big: new Array(1e6).fill(0) };
registry.register(obj, "obj");
obj = null; // しばらくするとログが出る(GCタイミングは保証されない)
6. マイクロベンチマーク — 関数単位の速度比較
6-1. Benchmark.jsの基本
npm install benchmark
import Benchmark from "benchmark";
const suite = new Benchmark.Suite();
suite
.add("for loop", () => {
let s = 0;
for (let i = 0; i < 1000; i++) s += i;
})
.add("reduce", () => {
Array.from({ length: 1000 }, (_, i) => i).reduce((a, b) => a + b, 0);
})
.on("cycle", (e: any) => console.log(String(e.target)))
.on("complete", function (this: any) {
console.log("Fastest is " + this.filter("fastest").map("name"));
})
.run({ async: true });
6-2. tinybench(モダンな後継)
npm install tinybench
import { Bench } from "tinybench";
const bench = new Bench({ time: 1000 });
bench
.add("Array.from", () => Array.from({ length: 1000 }, (_, i) => i))
.add("for push", () => {
const a: number[] = [];
for (let i = 0; i < 1000; i++) a.push(i);
});
await bench.run();
console.table(bench.table());
6-3. mitata(超軽量・高精度)
npm install mitata
import { run, bench, group } from "mitata";
group("string concat", () => {
bench("+ operator", () => {
let s = "";
for (let i = 0; i < 1000; i++) s += "a";
return s;
});
bench("Array.join", () => {
const a: string[] = [];
for (let i = 0; i < 1000; i++) a.push("a");
return a.join("");
});
});
await run();
6-4. ベンチマークの「嘘」を避けるコツ
// JITがdead-code eliminationしないように戻り値を使う
import { bench, run } from "mitata";
let sink: number;
bench("compute", () => {
let s = 0;
for (let i = 0; i < 1e4; i++) s += Math.sqrt(i);
sink = s; // 副作用を残してDCEを防ぐ
});
await run();
console.log(sink);
7. Chrome DevTools Performance Profiler
7-1. 録画の基本フロー
- DevToolsを開き Performance タブへ
- 左上の録画ボタン(または
Ctrl+E) - 計測したい操作を実行(5〜15秒推奨)
- 録画停止→Flame Chartで重い関数を探す
7-2. console.profile() でコードから録画開始
// 関数内の特定区間だけプロファイルを取れる
console.profile("heavyTask");
runHeavyTask();
console.profileEnd("heavyTask");
7-3. CPU Profile JSON の出力と解析
// Node.jsの場合: --cpu-prof フラグでJSON出力できる
// node --cpu-prof --cpu-prof-name=app.cpuprofile app.js
// 出力された .cpuprofile を Chrome DevTools の Performance タブにドロップすれば閲覧可能
7-4. Performance.profileを動的に取る(–inspect)
// Node.jsを --inspect で起動し、chrome://inspect から接続
// Profilerタブで Start → 操作 → Stop で .cpuprofile が得られる
7-5. Memory Snapshot(ヒープスナップショット)
DevTools の Memory タブで Heap snapshot を取得→Comparison ビューで「2回スナップショット間に増えたオブジェクト」を確認するのがリーク追跡の王道です。
7-6. Allocation Profile(タイムライン上の確保)
Memoryタブの Allocation instrumentation on timeline で、いつどんなオブジェクトが確保されたかを時系列で追えます。短時間に大量の小オブジェクトが確保される箇所がGC圧の原因です。
7-7. Coverage タブで未使用コードを発見
DevTools の Coverage タブを開き、ページをリロードすると JS/CSS の使用率が表示されます。初回ロードで50%以上使われていないファイルはコード分割の候補です。
8. Lighthouse / Lighthouse CI
8-1. Lighthouse CLIインストールと実行
npm install -g lighthouse
lighthouse https://example.com --view --output=html --output-path=./report.html
8-2. JSONで構造化結果を保存
lighthouse https://example.com
--output=json --output-path=./lh.json
--only-categories=performance
--chrome-flags="--headless"
8-3. プログラムから呼ぶ
import lighthouse from "lighthouse";
import { launch } from "chrome-launcher";
const chrome = await launch({ chromeFlags: ["--headless"] });
const result = await lighthouse("https://example.com", {
port: chrome.port,
output: "json",
onlyCategories: ["performance"],
});
console.log("score:", result.lhr.categories.performance.score * 100);
await chrome.kill();
8-4. Lighthouse CIで予算管理(lighthouserc.js)
module.exports = {
ci: {
collect: {
url: ["https://example.com/"],
numberOfRuns: 3,
},
assert: {
assertions: {
"categories:performance": ["error", { minScore: 0.9 }],
"largest-contentful-paint": ["warn", { maxNumericValue: 2500 }],
"interactive": ["error", { maxNumericValue: 3500 }],
},
},
upload: { target: "temporary-public-storage" },
},
};
8-5. GitHub ActionsでCI化する
name: Lighthouse CI
on: [push]
jobs:
lhci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run build
- run: npx @lhci/cli@0.13.x autorun
8-6. WebPageTestのAPIから走らせる
// WPTはCrUXより細かい waterfall を返す
const res = await fetch(
`https://www.webpagetest.org/runtest.php?url=${encodeURIComponent("https://example.com")}&k=${API_KEY}&f=json`
);
const data = await res.json();
console.log(data.data.userUrl);
9. RUM(Real User Monitoring)— 実ユーザー計測
9-1. web-vitalsをsendBeaconでバックエンドへ送る
import { onLCP, onINP, onCLS } from "web-vitals";
function send(metric: any) {
const body = JSON.stringify({ name: metric.name, value: metric.value, id: metric.id, url: location.pathname });
// sendBeaconは離脱時でも確実に飛ぶ
navigator.sendBeacon("/api/rum", body);
}
onLCP(send);
onINP(send);
onCLS(send);
9-2. Sentry Performance連携
import * as Sentry from "@sentry/browser";
import { browserTracingIntegration } from "@sentry/browser";
Sentry.init({
dsn: "https://xxx@oXXXX.ingest.sentry.io/XXXX",
integrations: [browserTracingIntegration()],
tracesSampleRate: 0.2, // 20%サンプリング
});
// 任意処理を計測
Sentry.startSpan({ name: "checkout", op: "ui.action" }, async () => {
await runCheckout();
});
9-3. DataDog RUM導入
import { datadogRum } from "@datadog/browser-rum";
datadogRum.init({
applicationId: "xxxx",
clientToken: "pubxxxx",
site: "datadoghq.com",
service: "my-app",
sessionSampleRate: 100,
sessionReplaySampleRate: 10,
trackUserInteractions: true,
});
datadogRum.startSessionReplayRecording();
9-4. Google Analytics 4にWeb Vitalsを送る
import { onLCP, onINP, onCLS } from "web-vitals";
function sendToGA4(metric: any) {
// gtagはGA4のグローバル関数
// @ts-ignore
window.gtag?.("event", metric.name, {
value: Math.round(metric.name === "CLS" ? metric.value * 1000 : metric.value),
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
metric_rating: metric.rating,
});
}
onLCP(sendToGA4);
onINP(sendToGA4);
onCLS(sendToGA4);
9-5. Service Worker経由でRUMをまとめて送る
// SWでバッチングするとビーコン回数を減らせる
self.addEventListener("message", (e) => {
if (e.data?.type === "rum") {
// BroadcastChannelやIndexedDBに蓄積→定期flush
queueRum(e.data.payload);
}
});
async function flush() {
const batch = await drainQueue();
if (batch.length) {
await fetch("/api/rum/batch", { method: "POST", body: JSON.stringify(batch), keepalive: true });
}
}
setInterval(flush, 10_000);
9-6. クライアント側からSWへmetricを送る
import { onINP } from "web-vitals";
onINP((m) => {
navigator.serviceWorker.controller?.postMessage({
type: "rum",
payload: { name: m.name, value: m.value, url: location.pathname, t: Date.now() },
});
});
10. Node.js のパフォーマンス計測
10-1. perf_hooks の基本
import { performance, PerformanceObserver } from "node:perf_hooks";
const obs = new PerformanceObserver((items) => {
for (const e of items.getEntries()) {
console.log(e.name, e.duration.toFixed(2), "ms");
}
});
obs.observe({ entryTypes: ["measure"] });
performance.mark("a");
await new Promise((r) => setTimeout(r, 100));
performance.mark("b");
performance.measure("a-to-b", "a", "b");
10-2. Node.jsのEvent Loop遅延を計測する
import { monitorEventLoopDelay } from "node:perf_hooks";
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
console.log({
min: h.min / 1e6,
mean: h.mean / 1e6,
max: h.max / 1e6,
p99: h.percentile(99) / 1e6,
});
h.reset();
}, 5000);
10-3. clinic.js Doctor で総合診断
npm install -g clinic
clinic doctor -- node server.js
# 終了後ブラウザでレポートが開き、ボトルネックの種類(CPU/IO/GC/EventLoop)を分類してくれる
10-4. clinic flame でフレームグラフを取る
clinic flame -- node server.js
10-5. autocannon で負荷をかけながら計測
npm install -g autocannon
autocannon -c 100 -d 30 http://localhost:3000/
10-6. –prof で V8プロファイルを取る
node --prof app.js
# 完了後 isolate-*.log が生成される
node --prof-process isolate-*.log > processed.txt
11. OpenTelemetry — 分散トレーシング
11-1. ブラウザのオートインストルメンテーション
npm install @opentelemetry/sdk-trace-web @opentelemetry/instrumentation-fetch
import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
const provider = new WebTracerProvider();
provider.register();
registerInstrumentations({
instrumentations: [new FetchInstrumentation({ propagateTraceHeaderCorsUrls: [/.+/] })],
});
11-2. Node.js側でtraceを受け取る
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
const sdk = new NodeSDK({
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
11-3. 手動spanで業務処理を計測
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("checkout");
await tracer.startActiveSpan("validate-cart", async (span) => {
try {
await validateCart();
} finally {
span.end();
}
});
12. CI/CDに組み込む実践パターン
12-1. パフォーマンスバジェットを設定する
// budget.json
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "stylesheet", "budget": 50 },
{ "resourceType": "image", "budget": 500 }
],
"timings": [
{ "metric": "interactive", "budget": 3500 },
{ "metric": "first-contentful-paint", "budget": 1800 }
]
}
]
12-2. CIで bundlesize / size-limit を使う
npm install --save-dev size-limit @size-limit/preset-app
// package.json
{
"size-limit": [
{ "path": "dist/main.*.js", "limit": "200 KB" }
],
"scripts": { "size": "size-limit" }
}
12-3. PRごとに数値差分をコメントする
// GitHub Actionsでサイズ差分をPRコメント
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
12-4. 退行検知のしきい値設計
本番ベンチマークのp75を基準に「5%以上の悪化でwarn・10%以上でfail」と段階を分けると、ノイズによる過剰アラートを避けつつ退行を逃しません。
13. 計測データを「行動」につなげるためのチェックリスト
13-1. 計測する前に決めるべき5つ
- 計測対象のユーザー操作(ログイン、カート追加 など)
- 計測する指標(LCP・INP・カスタムmark・APIレイテンシ)
- 計測の粒度(p50/p75/p95のどれを基準にするか)
- 許容できる退行幅(例: p75で+50ms以内)
- 退行時の巻き戻し方針(自動revert / manual roll-out)
13-2. 改善後に「再計測してから喜ぶ」
改善コミットの直後に必ず同じシナリオで再計測し、想定通りの差分が出ているかを確認します。Lighthouseは実行ごとに揺れるため、3回平均を基本にしましょう。
13-3. ダッシュボードに「劣化トレンド」を出す
単発のスコアではなく、過去30日のp75トレンドを可視化すると、リリースとの相関やじわじわ悪化するパターンが見えるようになります。
14. まとめ — 「測れない最適化」を卒業しよう
JavaScriptのパフォーマンス計測は、マイクロ(関数)・ページ・フィールドの3レイヤーを使い分けることが最重要です。
- 関数単位は tinybench / mitata / Benchmark.js
- ページ単位は Performance API + PerformanceObserver + Lighthouse
- フィールドは web-vitals + Sentry / DataDog / GA4 / OpenTelemetry
- Node.jsは perf_hooks + clinic.js + –prof
- CIでは Lighthouse CI + size-limit + パフォーマンスバジェット
すべてを最初から導入する必要はありません。まずはweb-vitalsをGA4に送るところから始め、ダッシュボードで「何が遅いか」を見えるようにしましょう。そこから初めて、本記事のProfilerやベンチマークが「効く」ようになります。「測れない最適化は祈り」を脱して、数値で語れるエンジニアになっていきましょう。
15. パフォーマンス学習をキャリアに変える
パフォーマンス計測・改善のスキルは、フロントエンドエンジニアの中でも特に評価が高く、年収アップやリードエンジニア登用の決め手になります。独学が難しいと感じたら、現役エンジニアによるメンタリング付きスクールで体系的に学ぶのが最短ルートです。
- テックアカデミー: 短期集中でフロントエンド+パフォーマンスチューニングを学べる。週2回のメンタリングで疑問即解決。
- 侍エンジニア: 完全マンツーマン。LighthouseやWeb Vitalsをポートフォリオ案件で実装まで指導してもらえる。
- DMM WEBCAMP: 転職保証付き。実務想定のチーム開発で計測→改善のサイクルを経験できる。
- レバテックキャリア(現役向け): パフォーマンス改善実績を年収交渉に直結させたい方に。フロントの上位案件・年収700万〜1200万のオファーが多数。
「測れる人」は、現場で圧倒的に重宝されます。本記事のコードを手元で1つずつ動かしながら、ぜひ次のキャリアステップへ繋げてください。

コメント