Performance vitals
Skill ComeOnOliver/skillshub/skills/aiskillstore/marketplace/doyajin174/performance-vitals
๐ง The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.
npx -y skills add ComeOnOliver/skillshub --skill performance-vitalsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Enforce Core Web Vitals optimization. Use when building user-facing features, reviewing performance, or when Lighthouse scores drop. Covers LCP, FID/INP, CLS, and optimization techniques.
The file declares its own license as MIT. That is the authorโs claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.2 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
Performance & Core Web Vitals
Core Web Vitals ์ต์ ํ๋ฅผ ๊ฐ์ ํ๋ ์คํฌ์ ๋๋ค.
2025 Context
2024๋ 3์: INP(Interaction to Next Paint)๊ฐ FID๋ฅผ ๋์ฒด Google Search ๋ญํน์ Core Web Vitals ์ํฅ
Core Web Vitals ๋ชฉํ
| ์งํ | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | โค 2.5s | 2.5s - 4s | > 4s |
| INP (Interaction to Next Paint) | โค 200ms | 200ms - 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | โค 0.1 | 0.1 - 0.25 | > 0.25 |
LCP ์ต์ ํ (Largest Contentful Paint)
๋ฌธ์ ์์ธ
- ๋๋ฆฐ ์๋ฒ ์๋ต
- ๋ ๋ ์ฐจ๋จ ๋ฆฌ์์ค (CSS, JS)
- ๋๋ฆฐ ๋ฆฌ์์ค ๋ก๋ฉ
- ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ ๋๋ง
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. ์ด๋ฏธ์ง ์ต์ ํ
// โ BAD: ์ต์ ํ ์๋ ์ด๋ฏธ์ง
<img src="/hero.jpg" alt="Hero" />
// โ
GOOD: Next.js Image ์ปดํฌ๋ํธ
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // LCP ์ด๋ฏธ์ง๋ priority ์ถ๊ฐ
placeholder="blur"
blurDataURL={blurDataUrl}
/>
2. ํฐํธ ์ต์ ํ
// โ
GOOD: Next.js ํฐํธ ์ต์ ํ
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // FOIT ๋ฐฉ์ง
preload: true,
});
// CSS
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
}
3. Critical CSS ์ธ๋ผ์ธ
// next.config.js
module.exports = {
experimental: {
optimizeCss: true,
},
};
4. ํ๋ฆฌ๋ก๋
<!-- ์ค์ ๋ฆฌ์์ค ํ๋ฆฌ๋ก๋ -->
<link rel="preload" href="/hero.jpg" as="image" />
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin />
<link rel="preconnect" href="https://api.example.com" />
INP ์ต์ ํ (Interaction to Next Paint)
๋ฌธ์ ์์ธ
- ๊ธด JavaScript ํ์คํฌ
- ๊ณผ๋ํ DOM ํฌ๊ธฐ
- ๋นํจ์จ์ ์ธ ์ด๋ฒคํธ ํธ๋ค๋ฌ
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. ๊ธด ํ์คํฌ ๋ถํ
// โ BAD: ๊ธด ๋๊ธฐ ์์
function processLargeData(items: Item[]) {
items.forEach(item => {
heavyComputation(item); // ๋ฉ์ธ ์ค๋ ๋ ๋ธ๋กํน
});
}
// โ
GOOD: ์ฒญํฌ ๋ถํ + yield
async function processLargeData(items: Item[]) {
const CHUNK_SIZE = 100;
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
chunk.forEach(item => heavyComputation(item));
// ๋ธ๋ผ์ฐ์ ์ ์ ์ด๊ถ ๋ฐํ
await new Promise(resolve => setTimeout(resolve, 0));
}
}
2. ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ต์ ํ
// โ BAD: ๋ฌด๊ฑฐ์ด ํธ๋ค๋ฌ
<input onChange={(e) => {
const value = e.target.value;
validateAllFields(); // ๋ฌด๊ฑฐ์ด ์ฐ์ฐ
updateUI();
sendAnalytics();
}} />
// โ
GOOD: ๋๋ฐ์ด์ค + ๋ถ๋ฆฌ
import { useDeferredValue, useTransition } from 'react';
function SearchInput() {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input);
const [isPending, startTransition] = useTransition();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value); // ์ฆ์ ์
๋ฐ์ดํธ
startTransition(() => {
// ๋ ๊ธํ ์
๋ฐ์ดํธ๋ transition์ผ๋ก
performSearch(e.target.value);
});
};
return <input value={input} onChange={handleChange} />;
}
3. Web Worker ํ์ฉ
// worker.ts
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.postMessage(data);
worker.onmessage = (e) => {
setResult(e.data);
};
CLS ์ต์ ํ (Cumulative Layout Shift)
๋ฌธ์ ์์ธ
- ํฌ๊ธฐ ๋ฏธ์ง์ ์ด๋ฏธ์ง/๋น๋์ค
- ๋์ ์ฝํ ์ธ ์ฝ์
- ์นํฐํธ FOIT/FOUT
- ์ ๋๋ฉ์ด์
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. ์ด๋ฏธ์ง/๋น๋์ค ํฌ๊ธฐ ์ง์
// โ BAD: ํฌ๊ธฐ ๋ฏธ์ง์
<img src="/photo.jpg" alt="Photo" />
// โ
GOOD: ํฌ๊ธฐ ๋ช
์
<img
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
// โ
GOOD: aspect-ratio ์ฌ์ฉ
<div style={{ aspectRatio: '16/9' }}>
<img src="/video-thumb.jpg" alt="Video" style={{ width: '100%' }} />
</div>
2. ๋์ ์ฝํ ์ธ ๊ณต๊ฐ ํ๋ณด
// โ BAD: ๊ณต๊ฐ ์์ด ๋์ ์ฝ์
{isLoaded && <Banner />}
// โ
GOOD: ์ค์ผ๋ ํค์ผ๋ก ๊ณต๊ฐ ํ๋ณด
{isLoaded ? <Banner /> : <BannerSkeleton />}
// โ
GOOD: min-height๋ก ๊ณต๊ฐ ํ๋ณด
<div style={{ minHeight: '200px' }}>
{isLoaded && <DynamicContent />}
</div>
3. ํฐํธ ๋ก๋ฉ
/* โ
GOOD: font-display: swap */
@font-face {
font-family: 'CustomFont';
src: url('/font.woff2') format('woff2');
font-display: swap;
}
/* โ
GOOD: ํด๋ฐฑ ํฐํธ ํฌ๊ธฐ ์กฐ์ */
@font-face {
font-family: 'CustomFont';
src: url('/font.woff2') format('woff2');
font-display: swap;
size-adjust: 105%; /* ํด๋ฐฑ ํฐํธ์ ํฌ๊ธฐ ๋ง์ถค */
}
4. ์ ๋๋ฉ์ด์
/* โ BAD: ๋ ์ด์์ ์์ฑ ์ ๋๋ฉ์ด์
*/
.animate {
transition: width 0.3s, height 0.3s, margin 0.3s;
}
/* โ
GOOD: transform, opacity๋ง ์ฌ์ฉ */
.animate {
transition: transform 0.3s, opacity 0.3s;
}
์ธก์ ๋๊ตฌ
Lighthouse CI
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://example.com/
https://example.com/dashboard
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
lighthouse-budget.json
[
{
"path": "/*",
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 },
{ "metric": "interaction-to-next-paint", "budget": 200 }
]
}
]
web-vitals ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import { onCLS, onINP, onLCP } from 'web-vitals';
function sendToAnalytics(metric: Metric) {
console.log(metric.name, metric.value);
// Analytics ์ ์ก
gtag('event', metric.name, {
value: metric.delta,
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta,
});
}
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
๋น ๋ฅธ ์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ
LCP ๊ฐ์
- LCP ์ด๋ฏธ์ง์
priority๋๋preload - ์ด๋ฏธ์ง ํฌ๋งท ์ต์ ํ (WebP/AVIF)
- ์๋ฒ ์๋ต ์๊ฐ < 600ms
- Critical CSS ์ธ๋ผ์ธ
- ๋ ๋ ์ฐจ๋จ ๋ฆฌ์์ค ์ ๊ฑฐ
INP ๊ฐ์
- Long Task > 50ms ์ ๊ฑฐ
- ์ด๋ฒคํธ ํธ๋ค๋ฌ ์ต์ ํ
- ๋ฌด๊ฑฐ์ด ์ฐ์ฐ Web Worker๋ก ์ด๋
-
useTransition,useDeferredValueํ์ฉ - Third-party ์คํฌ๋ฆฝํธ ์ง์ฐ ๋ก๋ฉ
CLS ๊ฐ์
- ๋ชจ๋ ์ด๋ฏธ์ง/๋น๋์ค ํฌ๊ธฐ ์ง์
- ๋์ ์ฝํ ์ธ ๊ณต๊ฐ ํ๋ณด (์ค์ผ๋ ํค)
-
font-display: swap์ค์ - transform/opacity๋ง ์ ๋๋ฉ์ด์
Workflow
1. ์ฑ๋ฅ ์ธก์
# Lighthouse CLI
npx lighthouse https://example.com --output=json --output-path=./lh-report.json
# ๋๋ Chrome DevTools > Lighthouse ํญ
2. ๋ฌธ์ ์ง๋จ
LCP > 2.5s?
โ Network ํญ์์ LCP ๋ฆฌ์์ค ํ์ธ
โ ์ด๋ฏธ์ง ์ต์ ํ / ํ๋ฆฌ๋ก๋ ์ ์ฉ
INP > 200ms?
โ Performance ํญ์์ Long Task ํ์ธ
โ ํ์คํฌ ๋ถํ / Web Worker ์ ์ฉ
CLS > 0.1?
โ Layout Shift ์์ธ ์์ ํ์ธ
โ ํฌ๊ธฐ ์ง์ / ์ค์ผ๋ ํค ์ ์ฉ
3. ๋ชจ๋ํฐ๋ง
ํ๋ก๋์
:
- Google Search Console Core Web Vitals
- Real User Monitoring (RUM)
- web-vitals ๋ผ์ด๋ธ๋ฌ๋ฆฌ
CI/CD:
- Lighthouse CI
- Performance Budget ์ค์