143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
||
import { requireAuth } from '@/lib/auth'
|
||
import lighthouse from 'lighthouse'
|
||
import * as chromeLauncher from 'chrome-launcher'
|
||
|
||
export const runtime = 'nodejs'
|
||
|
||
const OPPORTUNITY_IDS = [
|
||
'render-blocking-resources',
|
||
'unused-css-rules',
|
||
'unused-javascript',
|
||
'uses-optimized-images',
|
||
'uses-webp-images',
|
||
'uses-text-compression',
|
||
'uses-responsive-images',
|
||
'offscreen-images',
|
||
'server-response-time',
|
||
'redirects',
|
||
'efficient-animated-content',
|
||
'legacy-javascript',
|
||
'unminified-css',
|
||
'unminified-javascript',
|
||
]
|
||
|
||
const DIAGNOSTIC_IDS = [
|
||
'total-byte-weight',
|
||
'dom-size',
|
||
'bootup-time',
|
||
'mainthread-work-breakdown',
|
||
'font-display',
|
||
'third-party-summary',
|
||
'critical-request-chains',
|
||
]
|
||
|
||
function pickAudit(audit: any) {
|
||
if (!audit) return null
|
||
return {
|
||
id: audit.id,
|
||
title: audit.title,
|
||
description: audit.description,
|
||
score: audit.score,
|
||
scoreDisplayMode: audit.scoreDisplayMode,
|
||
displayValue: audit.displayValue ?? null,
|
||
numericValue: audit.numericValue ?? null,
|
||
}
|
||
}
|
||
|
||
export async function GET(request: NextRequest) {
|
||
const authErr = await requireAuth(request)
|
||
if (authErr) return authErr
|
||
|
||
const { searchParams } = new URL(request.url)
|
||
const url = searchParams.get('url')
|
||
const strategy = searchParams.get('strategy') || 'mobile'
|
||
|
||
if (!url) {
|
||
return NextResponse.json({ error: 'URL gerekli' }, { status: 400 })
|
||
}
|
||
|
||
try {
|
||
new URL(url)
|
||
} catch {
|
||
return NextResponse.json({ error: 'Geçersiz URL formatı' }, { status: 400 })
|
||
}
|
||
|
||
let chrome: chromeLauncher.LaunchedChrome | undefined
|
||
|
||
try {
|
||
chrome = await chromeLauncher.launch({
|
||
chromeFlags: ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
||
})
|
||
|
||
const formFactor = strategy === 'desktop' ? 'desktop' : 'mobile'
|
||
|
||
const screenEmulation = formFactor === 'desktop'
|
||
? { mobile: false, width: 1350, height: 940, deviceScaleFactor: 1, disabled: false }
|
||
: { mobile: true, width: 375, height: 812, deviceScaleFactor: 3, disabled: false }
|
||
|
||
// PSI ile eşleşen throttling değerleri
|
||
const throttling = formFactor === 'desktop'
|
||
? { rttMs: 40, throughputKbps: 10_240, uploadThroughputKbps: 10_240, cpuSlowdownMultiplier: 1 }
|
||
: { rttMs: 150, throughputKbps: 1_638.4, uploadThroughputKbps: 750, cpuSlowdownMultiplier: 4 }
|
||
|
||
const runnerResult = await Promise.race([
|
||
lighthouse(url, {
|
||
port: chrome.port,
|
||
output: 'json',
|
||
logLevel: 'error',
|
||
formFactor,
|
||
screenEmulation,
|
||
throttling,
|
||
throttlingMethod: 'simulate',
|
||
}),
|
||
new Promise<never>((_, reject) =>
|
||
setTimeout(() => reject(new Error('Lighthouse zaman aşımına uğradı')), 60_000)
|
||
),
|
||
])
|
||
|
||
if (!runnerResult) throw new Error('Lighthouse sonuç döndürmedi')
|
||
|
||
const { lhr } = runnerResult
|
||
|
||
return NextResponse.json({
|
||
id: url,
|
||
lighthouseResult: {
|
||
categories: {
|
||
performance: { score: lhr.categories?.performance?.score ?? 0 },
|
||
accessibility: { score: lhr.categories?.accessibility?.score ?? 0 },
|
||
'best-practices': { score: lhr.categories?.['best-practices']?.score ?? 0 },
|
||
seo: { score: lhr.categories?.seo?.score ?? 0 },
|
||
},
|
||
audits: {
|
||
'first-contentful-paint': pickAudit(lhr.audits?.['first-contentful-paint']),
|
||
'largest-contentful-paint': pickAudit(lhr.audits?.['largest-contentful-paint']),
|
||
'total-blocking-time': pickAudit(lhr.audits?.['total-blocking-time']),
|
||
'cumulative-layout-shift': pickAudit(lhr.audits?.['cumulative-layout-shift']),
|
||
'speed-index': pickAudit(lhr.audits?.['speed-index']),
|
||
'interactive': pickAudit(lhr.audits?.['interactive']),
|
||
},
|
||
opportunities: OPPORTUNITY_IDS
|
||
.map(id => pickAudit(lhr.audits?.[id]))
|
||
.filter((a): a is NonNullable<typeof a> => a !== null && a.score !== null && a.score < 0.9),
|
||
diagnostics: DIAGNOSTIC_IDS
|
||
.map(id => pickAudit(lhr.audits?.[id]))
|
||
.filter((a): a is NonNullable<typeof a> => a !== null),
|
||
},
|
||
})
|
||
} catch (error: any) {
|
||
console.error('Lighthouse hatası:', error)
|
||
return NextResponse.json(
|
||
{
|
||
error: error.message.includes('zaman aşımı')
|
||
? 'Analiz zaman aşımına uğradı, lütfen tekrar deneyin.'
|
||
: 'Analiz sırasında hata oluştu. Sunucuda Chrome yüklü olduğundan emin olun.',
|
||
details: error?.message || String(error)
|
||
},
|
||
{ status: 500 }
|
||
)
|
||
} finally {
|
||
await chrome?.kill()
|
||
}
|
||
}
|