Skip to content

Commit bf2dad9

Browse files
committed
Update lighthouse.yml
1 parent 3caffcb commit bf2dad9

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

.github/workflows/lighthouse.yml

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Lighthouse CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
inputs:
9+
url:
10+
description: 'URL to run Lighthouse on'
11+
required: false
12+
default: 'http://localhost:8080'
13+
14+
permissions:
15+
issues: write
16+
contents: read
17+
18+
jobs:
19+
lighthouse-audit:
20+
name: Lighthouse Audit
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Use Node.js 20
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: 20
29+
30+
- name: Install dependencies
31+
run: |
32+
npm install -g @lhci/[email protected]
33+
npm install -g http-server
34+
35+
- name: Start local server
36+
run: http-server . -p 8080 &
37+
38+
- name: Run Lighthouse CI
39+
id: lighthouse
40+
continue-on-error: true
41+
run: |
42+
URL="${{ github.event.inputs.url || 'http://localhost:8080' }}"
43+
lhci autorun --collect.url=$URL
44+
45+
- name: Create GitHub Issue with Results
46+
if: always()
47+
uses: actions/github-script@v6
48+
with:
49+
github-token: ${{ github.token }}
50+
script: |
51+
const fs = require('fs');
52+
const path = require('path');
53+
54+
const lhciDir = '.lighthouseci';
55+
const jsonReports = fs.readdirSync(lhciDir).filter(f => f.endsWith('.json'));
56+
const latestReport = jsonReports.sort().reverse()[0];
57+
const reportPath = path.join(lhciDir, latestReport);
58+
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
59+
60+
const formatScore = (value = 0) => {
61+
return Math.round(value * 100);
62+
};
63+
64+
const getEmoji = (value, metric) => {
65+
const thresholds = {
66+
LCP: { good: 2500, needsImprovement: 4000 },
67+
INP: { good: 200, needsImprovement: 500 },
68+
CLS: { good: 0.1, needsImprovement: 0.25 },
69+
};
70+
71+
if (!thresholds[metric]) return value >= 90 ? '🟢' : value >= 50 ? '🟠' : '🔴';
72+
73+
const t = thresholds[metric];
74+
return value <= t.good ? '🟢' : value <= t.needsImprovement ? '🟠' : '🔴';
75+
};
76+
77+
const formatMetric = (value, metric) => {
78+
if (!value) return 'N/A';
79+
if (metric === 'CLS') return value.toFixed(3);
80+
return `${(value / 1000).toFixed(2)}s`;
81+
};
82+
83+
const getLighthouseResult = (report, category) => {
84+
try {
85+
return report.categories?.[category]?.score ?? 0;
86+
} catch (e) {
87+
return 0;
88+
}
89+
};
90+
91+
const getMetricValue = (report, metric) => {
92+
try {
93+
return report.audits?.[metric]?.numericValue ?? 0;
94+
} catch (e) {
95+
return 0;
96+
}
97+
};
98+
99+
const lighthouseScores = {
100+
performance: getLighthouseResult(report, 'performance'),
101+
accessibility: getLighthouseResult(report, 'accessibility'),
102+
'best-practices': getLighthouseResult(report, 'best-practices'),
103+
seo: getLighthouseResult(report, 'seo'),
104+
pwa: getLighthouseResult(report, 'pwa')
105+
};
106+
107+
const webVitals = {
108+
LCP: getMetricValue(report, 'largest-contentful-paint'),
109+
INP: getMetricValue(report, 'experimental-interaction-to-next-paint'),
110+
CLS: getMetricValue(report, 'cumulative-layout-shift')
111+
};
112+
113+
const reportUrl = `.lighthouseci/${latestReport.replace('.json', '.html')}`;
114+
115+
const body = `## 🚨 웹사이트 성능 측정 결과
116+
117+
### 🎯 Lighthouse 점수
118+
| 카테고리 | 점수 | 상태 |
119+
|----------|------|------|
120+
| Performance | ${formatScore(lighthouseScores.performance)}% | ${getEmoji(formatScore(lighthouseScores.performance))} |
121+
| Accessibility | ${formatScore(lighthouseScores.accessibility)}% | ${getEmoji(formatScore(lighthouseScores.accessibility))} |
122+
| Best Practices | ${formatScore(lighthouseScores['best-practices'])}% | ${getEmoji(formatScore(lighthouseScores['best-practices']))} |
123+
| SEO | ${formatScore(lighthouseScores.seo)}% | ${getEmoji(formatScore(lighthouseScores.seo))} |
124+
| PWA | ${formatScore(lighthouseScores.pwa)}% | ${getEmoji(formatScore(lighthouseScores.pwa))} |
125+
126+
### 📊 Core Web Vitals (2024)
127+
| 메트릭 | 설명 | 측정값 | 상태 |
128+
|--------|------|--------|------|
129+
| LCP | Largest Contentful Paint | ${formatMetric(webVitals.LCP, 'LCP')} | ${getEmoji(webVitals.LCP, 'LCP')} |
130+
| INP | Interaction to Next Paint | ${formatMetric(webVitals.INP, 'INP')} | ${getEmoji(webVitals.INP, 'INP')} |
131+
| CLS | Cumulative Layout Shift | ${formatMetric(webVitals.CLS, 'CLS')} | ${getEmoji(webVitals.CLS, 'CLS')} |
132+
133+
### 📝 Core Web Vitals 기준값
134+
- **LCP (Largest Contentful Paint)**: 가장 큰 콘텐츠가 화면에 그려지는 시점
135+
- 🟢 Good: < 2.5s
136+
- 🟠 Needs Improvement: < 4.0s
137+
- 🔴 Poor: ≥ 4.0s
138+
139+
- **INP (Interaction to Next Paint)**: 사용자 상호작용에 대한 전반적인 응답성
140+
- 🟢 Good: < 200ms
141+
- 🟠 Needs Improvement: < 500ms
142+
- 🔴 Poor: ≥ 500ms
143+
144+
- **CLS (Cumulative Layout Shift)**: 페이지 로드 중 예기치 않은 레이아웃 변경의 정도
145+
- 🟢 Good: < 0.1
146+
- 🟠 Needs Improvement: < 0.25
147+
- 🔴 Poor: ≥ 0.25
148+
149+
> 📅 측정 시간: ${new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}`;
150+
151+
await github.rest.issues.create({
152+
owner: context.repo.owner,
153+
repo: context.repo.repo,
154+
title: `📊 웹사이트 성능 측정 결과 - ${new Date().toLocaleString('ko-KR', {
155+
timeZone: 'Asia/Seoul',
156+
year: 'numeric',
157+
month: '2-digit',
158+
day: '2-digit',
159+
hour: '2-digit',
160+
minute: '2-digit',
161+
hour12: false
162+
})}`,
163+
body: body,
164+
labels: ['lighthouse-audit', 'web-vitals']
165+
});

0 commit comments

Comments
 (0)