Elysia Tools
Navigation mobile
Edge Computing
Exemples Netlify Edge Functions
Exemples complets de Netlify Edge Functions couvrant middleware, authentification, A/B testing, géolocalisation et patterns avancés d'edge computing
Exemples
Entrées de cette collection
Middleware Edge de Base
Patterns essentiels de middleware edge pour le traitement des requêtes, modification des réponses et routage de base
Difficulté
3/10
Temps estimé
15 min
Étiquettes
netlify, edge functions, middleware
Prérequis
JavaScript ES6+, HTTP basics, Netlify edge functions
// Basic Edge Middleware Examples
// File: edge-functions/middleware.js
// 1. Request logging and analytics middleware
const edgeFunction1 = async (request, context) => {
const start = Date.now()
const url = new URL(request.url)
// Log request details
console.log({
method: request.method,
url: url.pathname,
userAgent: request.headers.get('user-agent'),
ip: getClientIP(request),
timestamp: new Date().toISOString()
})
// Continue to next middleware or page
const response = await context.next()
// Add response headers
const duration = Date.now() - start
response.headers.set('x-response-time', `${duration}ms`)
response.headers.set('x-edge-function', 'middleware')
return response
}
function getClientIP(request) {
return request.headers.get('x-nf-client-connection-ip') ||
request.headers.get('x-forwarded-for') ||
'0.0.0.0'
}
// 2. CORS middleware
// File: edge-functions/cors.js
const edgeFunction2 = async (request, context) => {
const response = await context.next()
// Set CORS headers
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')
response.headers.set('Access-Control-Max-Age', '86400')
// Handle preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, { status: 200, headers: response.headers })
}
return response
}
// 3. Security headers middleware
// File: edge-functions/security.js
const edgeFunction3 = async (request, context) => {
const response = await context.next()
// Set security headers
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
// Content Security Policy
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
"img-src 'self' data: https:",
"connect-src 'self' https://api.example.com"
].join('; ')
response.headers.set('Content-Security-Policy', csp)
return response
}
// 4. Path-based routing middleware
// File: edge-functions/router.js
const edgeFunction4 = async (request, context) => {
const url = new URL(request.url)
const pathname = url.pathname
// API routes
if (pathname.startsWith('/api/')) {
return handleAPIRoute(request, pathname)
}
// Admin routes
if (pathname.startsWith('/admin/')) {
return handleAdminRoute(request, context)
}
// Static assets with custom headers
if (pathname.startsWith('/static/') || pathname.startsWith('/_next/static/')) {
const response = await context.next()
response.headers.set('Cache-Control', 'public, max-age=31536000, immutable')
return response
}
// Default handling
return context.next()
}
async function handleAPIRoute(request, pathname) {
// Route API requests to different handlers
if (pathname.includes('/users')) {
return new Response(JSON.stringify({ users: [] }), {
headers: { 'Content-Type': 'application/json' }
})
}
if (pathname.includes('/products')) {
return new Response(JSON.stringify({ products: [] }), {
headers: { 'Content-Type': 'application/json' }
})
}
return new Response('API endpoint not found', { status: 404 })
}
async function handleAdminRoute(request, context) {
// Check authentication for admin routes
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 })
}
// Validate token (simplified)
const token = authHeader.slice(7)
if (!isValidAdminToken(token)) {
return new Response('Invalid token', { status: 401 })
}
return context.next()
}
function isValidAdminToken(token) {
// Implement proper token validation
return token.length > 10
}Géolocalisation et Localisation
Routage basé sur la géolocalisation, localisation de contenu et fonctionnalité spécifique à la région
Difficulté
6/10
Temps estimé
25 min
Étiquettes
netlify, edge functions, geolocation
Prérequis
JavaScript, Geolocation APIs, Content localization
// Geolocation and Localization Examples
// File: edge-functions/geolocation.js
const edgeFunction5 = async (request, context) => {
const url = new URL(request.url)
const geo = request.geo || {}
// Extract location data
const location = {
country: geo.country?.code?.toLowerCase() || 'unknown',
countryName: geo.country?.name || 'Unknown',
city: geo.city || 'Unknown',
region: geo.subdivision?.name || 'Unknown',
timezone: geo.timezone || 'UTC',
latitude: geo.latitude,
longitude: geo.longitude
}
console.log('User location:', location)
// Skip API routes
if (url.pathname.startsWith('/api/')) {
return context.next()
}
// Handle localized routes
if (url.pathname === '/' || url.pathname === '/home') {
return handleLocalizedHome(request, location, context)
}
// Handle product localization
if (url.pathname.startsWith('/products/')) {
return handleLocalizedProducts(request, location, context)
}
// Add location headers to all responses
const response = await context.next()
response.headers.set('x-user-country', location.country)
response.headers.set('x-user-city', location.city)
response.headers.set('x-user-timezone', location.timezone)
return response
}
async function handleLocalizedHome(request, location, context) {
const url = new URL(request.url)
// Redirect to localized version based on country
const localizedPaths = {
'us': '/us',
'gb': '/uk',
'de': '/de',
'fr': '/fr',
'es': '/es',
'it': '/it',
'jp': '/ja',
'cn': '/zh',
'br': '/pt-br'
}
const localizedPath = localizedPaths[location.country]
if (localizedPath && !url.pathname.startsWith(`/${location.country}`)) {
const localizedUrl = new URL(localizedPath, url)
return Response.redirect(localizedUrl, 302)
}
return context.next()
}
async function handleLocalizedProducts(request, location, context) {
const url = new URL(request.url)
// Add region-specific pricing
const response = await context.next()
if (response.headers.get('content-type')?.includes('text/html')) {
const html = await response.text()
// Inject pricing and availability script
const pricingScript = generatePricingScript(location)
const modifiedHtml = html.replace(
'</head>',
`${pricingScript}</head>`
)
return new Response(modifiedHtml, {
headers: response.headers
})
}
return response
}
function generatePricingScript(location) {
const pricing = {
'us': { currency: 'USD', symbol: '$', rate: 1 },
'gb': { currency: 'GBP', symbol: '£', rate: 0.79 },
'de': { currency: 'EUR', symbol: '€', rate: 0.92 },
'fr': { currency: 'EUR', symbol: '€', rate: 0.92 },
'es': { currency: 'EUR', symbol: '€', rate: 0.92 },
'it': { currency: 'EUR', symbol: '€', rate: 0.92 },
'br': { currency: 'BRL', symbol: 'R$', rate: 5.2 }
}
const localPricing = pricing[location.country] || pricing['us']
return `
<script>
window.LOCAL_PRICING = ${JSON.stringify(localPricing)};
window.USER_LOCATION = ${JSON.stringify(location)};
// Apply localized pricing
document.addEventListener('DOMContentLoaded', function() {
const priceElements = document.querySelectorAll('[data-base-price]');
priceElements.forEach(el => {
const basePrice = parseFloat(el.dataset.basePrice);
const localPrice = (basePrice * window.LOCAL_PRICING.rate).toFixed(2);
el.textContent = '${localPricing.symbol}' + localPrice;
});
});
</script>
`
}
// Geolocation-based content filtering
// File: edge-functions/content-filter.js
const edgeFunction6 = async (request, context) => {
const geo = request.geo || {}
const country = geo.country?.code?.toLowerCase()
const response = await context.next()
// Skip for non-HTML responses
if (!response.headers.get('content-type')?.includes('text/html')) {
return response
}
const html = await response.text()
// Apply content restrictions based on location
let modifiedHtml = html
// GDPR compliance for EU countries
const euCountries = ['de', 'fr', 'it', 'es', 'nl', 'be', 'at', 'se', 'dk', 'fi']
if (euCountries.includes(country)) {
modifiedHtml = addGDPRBanner(modifiedHtml)
}
// Content filtering for specific regions
if (country === 'cn') {
modifiedHtml = filterContentForChina(modifiedHtml)
}
return new Response(modifiedHtml, {
headers: response.headers
})
}
function addGDPRBanner(html) {
const banner = `
<div id="gdpr-banner" style="position: fixed; bottom: 0; left: 0; right: 0;
background: #333; color: white; padding: 1rem; z-index: 9999;
display: flex; justify-content: space-between; align-items: center;">
<span>This website uses cookies to ensure you get the best experience on our website.</span>
<div>
<button onclick="acceptCookies()" style="margin-right: 10px; padding: 5px 15px;
background: #4CAF50; color: white; border: none; cursor: pointer;">
Accept
</button>
<button onclick="rejectCookies()" style="padding: 5px 15px;
background: #f44336; color: white; border: none; cursor: pointer;">
Reject
</button>
</div>
</div>
<script>
function acceptCookies() {
document.getElementById('gdpr-banner').style.display = 'none';
localStorage.setItem('gdpr-consent', 'accepted');
}
function rejectCookies() {
document.getElementById('gdpr-banner').style.display = 'none';
localStorage.setItem('gdpr-consent', 'rejected');
}
if (localStorage.getItem('gdpr-consent')) {
document.getElementById('gdpr-banner').style.display = 'none';
}
</script>
`
return html.replace('</body>', banner + '</body>')
}
function filterContentForChina(html) {
// Remove or modify content not available in China
return html
.replace(/<iframe[^>]*youtube[^>]*><\/iframe>/gi,
'<div>Video content not available in your region</div>')
.replace(/<iframe[^>]*google[^>]*><\/iframe>/gi,
'<div>Google services not available in your region</div>')
}
// Timezone-aware content
// File: edge-functions/timezone.js
const edgeFunction7 = async (request, context) => {
const timezone = request.geo?.timezone || 'UTC'
const url = new URL(request.url)
const response = await context.next()
// Skip for non-HTML responses
if (!response.headers.get('content-type')?.includes('text/html')) {
return response
}
const html = await response.text()
// Inject timezone information
const timezoneScript = `
<script>
window.USER_TIMEZONE = '${timezone}';
window.USER_TIME = new Date().toLocaleString('en-US', {
timeZone: '${timezone}',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
// Convert timestamps to user timezone
function convertToLocalTime(utcTime) {
return new Date(utcTime).toLocaleString('en-US', {
timeZone: window.USER_TIMEZONE
});
}
// Update all timestamp elements
document.addEventListener('DOMContentLoaded', function() {
const timeElements = document.querySelectorAll('[data-utc-time]');
timeElements.forEach(el => {
const utcTime = el.dataset.utcTime;
el.textContent = convertToLocalTime(utcTime);
});
});
</script>
`
const modifiedHtml = html.replace('</head>', timezoneScript + '</head>')
return new Response(modifiedHtml, {
headers: response.headers
})
}Authentification et Autorisation
Système complet d'authentification avec validation JWT, gestion de session et contrôle d'accès basé sur les rôles
Difficulté
8/10
Temps estimé
35 min
Étiquettes
netlify, edge functions, security, auth
Prérequis
JWT, Authentication concepts, Cookie management, Role-based access control
// Authentication and Authorization Examples
// File: edge-functions/auth.js
const edgeFunction8 = async (request, context) => {
const url = new URL(request.url)
// Public routes that don't require authentication
const publicRoutes = ['/login', '/signup', '/forgot-password', '/api/auth/login', '/api/auth/register', '/_next/static/']
const isPublicRoute = publicRoutes.some(route => url.pathname.startsWith(route))
if (isPublicRoute) {
return context.next()
}
// Check for authentication
const authResult = await authenticateRequest(request)
if (!authResult.authenticated) {
return handleUnauthenticated(request, authResult.reason)
}
// Add user context to request
const response = await context.next()
response.headers.set('x-user-id', authResult.userId)
response.headers.set('x-user-role', authResult.role)
response.headers.set('x-user-email', authResult.email)
return response
}
async function authenticateRequest(request) {
const authHeader = request.headers.get('authorization')
const cookieHeader = request.headers.get('cookie') || ''
// Try JWT token from Authorization header
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.slice(7)
return await validateJWTToken(token)
}
// Try JWT token from cookies
const tokenMatch = cookieHeader.match(/token=([^;]+)/)
if (tokenMatch) {
const token = tokenMatch[1]
return await validateJWTToken(token)
}
return { authenticated: false, reason: 'no_token' }
}
async function validateJWTToken(token) {
try {
// Split JWT token
const [, payloadBase64] = token.split('.')
const payload = JSON.parse(atob(payloadBase64))
// Check expiration
if (payload.exp && Date.now() / 1000 > payload.exp) {
return { authenticated: false, reason: 'token_expired' }
}
// In production, verify signature with secret key
// For demo purposes, we'll just validate structure
if (!payload.sub || !payload.email) {
return { authenticated: false, reason: 'invalid_token' }
}
return {
authenticated: true,
userId: payload.sub,
email: payload.email,
role: payload.role || 'user',
permissions: payload.permissions || []
}
} catch (error) {
console.error('JWT validation error:', error)
return { authenticated: false, reason: 'invalid_token' }
}
}
function handleUnauthenticated(request, reason) {
const url = new URL(request.url)
// Return 401 for API routes
if (url.pathname.startsWith('/api/')) {
return new Response(JSON.stringify({
error: 'Authentication required',
reason: reason
}), {
status: 401,
headers: { 'Content-Type': 'application/json' }
})
}
// Redirect to login for web routes
const loginUrl = new URL('/login', url)
loginUrl.searchParams.set('redirect', url.pathname + url.search)
loginUrl.searchParams.set('reason', reason)
return Response.redirect(loginUrl, 302)
}
// Role-based access control
// File: edge-functions/rbac.js
const edgeFunction9 = async (request, context) => {
const url = new URL(request.url)
// Define protected routes and required roles
const protectedRoutes = {
'/admin': ['admin'],
'/admin/users': ['admin'],
'/admin/settings': ['admin'],
'/dashboard': ['user', 'admin', 'moderator'],
'/api/admin': ['admin'],
'/api/moderator': ['admin', 'moderator'],
'/profile': ['user', 'admin', 'moderator']
}
// Check if route requires specific role
const requiredRole = getRequiredRole(url.pathname, protectedRoutes)
if (requiredRole) {
const authResult = await authenticateRequest(request)
if (!authResult.authenticated) {
return handleUnauthenticated(request, 'no_token')
}
if (!hasRequiredRole(authResult.role, requiredRole)) {
return handleUnauthorized(authResult.role, requiredRole)
}
// Add user info to headers for downstream processing
const response = await context.next()
response.headers.set('x-user-id', authResult.userId)
response.headers.set('x-user-role', authResult.role)
response.headers.set('x-user-email', authResult.email)
return response
}
return context.next()
}
function getRequiredRole(pathname, protectedRoutes) {
for (const [route, roles] of Object.entries(protectedRoutes)) {
if (pathname.startsWith(route)) {
return roles
}
}
return null
}
function hasRequiredRole(userRole, requiredRoles) {
return requiredRoles.includes(userRole)
}
function handleUnauthorized(userRole, requiredRoles) {
const url = new URL(request.url)
// Return 403 for API routes
if (url.pathname.startsWith('/api/')) {
return new Response(JSON.stringify({
error: 'Insufficient permissions',
userRole: userRole,
requiredRoles: requiredRoles
}), {
status: 403,
headers: { 'Content-Type': 'application/json' }
})
}
// Show unauthorized page for web routes
return new Response(`
<!DOCTYPE html>
<html>
<head>
<title>Unauthorized Access</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: #f5f5f5;
}
.container {
text-align: center;
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 { color: #e74c3c; margin-bottom: 1rem; }
p { color: #666; margin-bottom: 1.5rem; }
button {
background: #3498db;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<h1>🚫 Unauthorized Access</h1>
<p>You don't have permission to access this resource.</p>
<p>Your role: <strong>${userRole}</strong></p>
<p>Required roles: <strong>${requiredRoles.join(', ')}</strong></p>
<button onclick="history.back()">Go Back</button>
</div>
</body>
</html>
`, {
status: 403,
headers: { 'Content-Type': 'text/html' }
})
}
// Session management with refresh tokens
// File: edge-functions/session.js
const edgeFunction10 = async (request, context) => {
const url = new URL(request.url)
// Skip for auth routes
if (url.pathname.startsWith('/api/auth/')) {
return context.next()
}
const cookieHeader = request.headers.get('cookie') || ''
const accessToken = getCookieValue(cookieHeader, 'access_token')
const refreshToken = getCookieValue(cookieHeader, 'refresh_token')
// Check access token
if (accessToken) {
const authResult = await validateJWTToken(accessToken)
if (authResult.authenticated) {
const response = await context.next()
addUserHeaders(response, authResult)
return response
}
// Access token expired, try refresh
if (refreshToken && (await shouldRefreshToken(accessToken))) {
const refreshResult = await refreshAccessToken(refreshToken)
if (refreshResult.success) {
const response = await context.next()
addAuthCookies(response, refreshResult.tokens)
addUserHeaders(response, refreshResult.user)
return response
}
}
}
// No valid tokens, redirect to login
if (!url.pathname.startsWith('/login')) {
const loginUrl = new URL('/login', url)
return Response.redirect(loginUrl, 302)
}
return context.next()
}
function getCookieValue(cookieHeader, name) {
const match = cookieHeader.match(new RegExp(`(^| )${name}=([^;]+)`))
return match ? match[2] : null
}
async function shouldRefreshToken(accessToken) {
try {
const [, payloadBase64] = accessToken.split('.')
const payload = JSON.parse(atob(payloadBase64))
// Refresh if token expires within 5 minutes
return payload.exp - (Date.now() / 1000) < 300
} catch {
return false
}
}
async function refreshAccessToken(refreshToken) {
try {
// In production, call your authentication service
// For demo, we'll simulate a refresh
const response = await fetch('https://your-auth-service.com/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${refreshToken}`
}
})
if (response.ok) {
const data = await response.json()
return {
success: true,
tokens: data.tokens,
user: data.user
}
}
return { success: false }
} catch (error) {
console.error('Token refresh error:', error)
return { success: false }
}
}
function addAuthCookies(response, tokens) {
const cookieOptions = 'Path=/; HttpOnly; Secure; SameSite=Strict'
response.headers.append('Set-Cookie', `access_token=${tokens.accessToken}; ${cookieOptions}`)
response.headers.append('Set-Cookie', `refresh_token=${tokens.refreshToken}; ${cookieOptions}`)
}
function addUserHeaders(response, user) {
response.headers.set('x-user-id', user.userId)
response.headers.set('x-user-email', user.email)
response.headers.set('x-user-role', user.role)
}Optimisation des Performances
Optimisation avancée des performances avec caching, compression, intégration CDN et optimisation des réponses
Difficulté
9/10
Temps estimé
45 min
Étiquettes
netlify, edge functions, performance, optimization
Prérequis
Performance optimization, Caching strategies, Web vitals, Service workers
// Performance Optimization Examples
// File: edge-functions/performance.js
const edgeFunction11 = async (request, context) => {
const url = new URL(request.url)
// Skip API routes and static assets
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/static/')) {
return context.next()
}
const response = await context.next()
// Optimize based on content type
const contentType = response.headers.get('content-type') || ''
if (contentType.includes('text/html')) {
return optimizeHTMLResponse(request, response)
} else if (contentType.includes('application/json')) {
return optimizeJSONResponse(response)
} else if (contentType.includes('text/css')) {
return optimizeCSSResponse(response)
} else if (contentType.includes('application/javascript')) {
return optimizeJSResponse(response)
}
return optimizeResponse(response)
}
async function optimizeHTMLResponse(request, response) {
const html = await response.text()
// Critical CSS inlining
const optimizedHTML = await inlineCriticalCSS(html)
// Resource hints for performance
const resourceHints = generateResourceHints()
// Service worker registration
const serviceWorkerScript = generateServiceWorkerScript()
// Performance monitoring script
const performanceScript = generatePerformanceScript()
const modifiedHTML = optimizedHTML
.replace('</head>', resourceHints + serviceWorkerScript + performanceScript + '</head>')
.replace('</body>', generatePerformanceBanner() + '</body>')
// Add performance headers
const performanceHeaders = {
'Cache-Control': 'public, max-age=3600, must-revalidate',
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'on'
}
const newResponse = new Response(modifiedHTML, {
status: response.status,
statusText: response.statusText,
headers: { ...Object.fromEntries(response.headers), ...performanceHeaders }
})
return newResponse
}
async function inlineCriticalCSS(html) {
// Extract critical CSS for above-the-fold content
const criticalCSS = await extractCriticalCSS(html)
// Find the first CSS link
const cssLinkMatch = html.match(/<link[^>]*rel=["']stylesheet["'][^>]*>/i)
if (cssLinkMatch && criticalCSS) {
// Insert critical CSS inline
const inlineCSS = `<style data-inline="critical">${criticalCSS}</style>`
return html.replace(cssLinkMatch[0], inlineCSS + cssLinkMatch[0])
}
return html
}
async function extractCriticalCSS(html) {
// Mock critical CSS extraction
// In production, use services like Penthouse or CriticalCSS
return `
body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; }
h1, h2, h3 { margin-top: 0; }
img { max-width: 100%; height: auto; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
`
}
function generateResourceHints() {
return `
<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="dns-prefetch" href="//api.example.com">
<!-- Preconnect for critical resources -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preload critical resources -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<!-- Prefetch likely next pages -->
<link rel="prefetch" href="/about">
<link rel="prefetch" href="/contact">
`
}
function generateServiceWorkerScript() {
return `
<script>
// Service Worker Registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
`
}
function generatePerformanceScript() {
return `
<script>
// Performance Monitoring
window.addEventListener('load', () => {
// Core Web Vitals
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
// Send to analytics
gtag('event', 'LCP', { value: entry.startTime });
}
if (entry.entryType === 'first-input') {
console.log('FID:', entry.processingStart - entry.startTime);
gtag('event', 'FID', { value: entry.processingStart - entry.startTime });
}
if (entry.entryType === 'layout-shift') {
if (!entry.hadRecentInput) {
console.log('CLS:', entry.value);
gtag('event', 'CLS', { value: entry.value });
}
}
}
});
observer.observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });
}
// Navigation timing
const navigation = performance.getEntriesByType('navigation')[0];
if (navigation) {
const loadTime = navigation.loadEventEnd - navigation.fetchStart;
console.log('Page load time:', loadTime);
gtag('event', 'page_load_time', { value: loadTime });
}
});
// Lazy loading for images
document.addEventListener('DOMContentLoaded', () => {
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
imageObserver.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
}
});
</script>
`
}
function generatePerformanceBanner() {
return `
<div id="performance-banner" style="position: fixed; bottom: 20px; right: 20px;
background: rgba(0,0,0,0.8); color: white; padding: 10px 15px;
border-radius: 5px; font-size: 12px; z-index: 9999; display: none;">
<div>Performance Metrics:</div>
<div id="perf-metrics"></div>
</div>
<script>
// Show performance metrics in development
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
document.getElementById('performance-banner').style.display = 'block';
setInterval(() => {
const metrics = {
memory: performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1048576) + 'MB' : 'N/A',
timing: Math.round(performance.now()) + 'ms'
};
document.getElementById('perf-metrics').innerHTML =
'Memory: ' + metrics.memory + ' | Time: ' + metrics.timing;
}, 1000);
}
</script>
`
}
function optimizeJSONResponse(response) {
// Compress JSON responses
const headers = {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300, must-revalidate',
'Vary': 'Accept-Encoding'
}
return new Response(response.body, {
status: response.status,
headers: { ...Object.fromEntries(response.headers), ...headers }
})
}
function optimizeCSSResponse(response) {
// Optimize CSS delivery
const headers = {
'Cache-Control': 'public, max-age=31536000, immutable',
'X-Content-Type-Options': 'nosniff'
}
return new Response(response.body, {
status: response.status,
headers: { ...Object.fromEntries(response.headers), ...headers }
})
}
function optimizeJSResponse(response) {
// Optimize JavaScript delivery
const headers = {
'Cache-Control': 'public, max-age=31536000, immutable',
'X-Content-Type-Options': 'nosniff'
}
return new Response(response.body, {
status: response.status,
headers: { ...Object.fromEntries(response.headers), ...headers }
})
}
function optimizeResponse(response) {
// General optimization for other content types
const headers = {
'Cache-Control': 'public, max-age=3600',
'Vary': 'Accept-Encoding'
}
return new Response(response.body, {
status: response.status,
headers: { ...Object.fromEntries(response.headers), ...headers }
})
}
// Advanced caching strategies
// File: edge-functions/caching.js
const edgeFunction12 = async (request, context) => {
const url = new URL(request.url)
const cacheKey = generateCacheKey(request)
// Try to get from cache first
const cachedResponse = await getFromCache(cacheKey)
if (cachedResponse && !shouldBypassCache(request)) {
return cachedResponse
}
const response = await context.next()
// Cache successful responses
if (response.ok) {
await setCache(cacheKey, response, getCacheDuration(request))
}
// Add cache headers
const cacheHeaders = generateCacheHeaders(request)
cacheHeaders.forEach((value, key) => {
response.headers.set(key, value)
})
return response
}
function generateCacheKey(request) {
const url = new URL(request.url)
const authHeader = request.headers.get('authorization')
const cookieHeader = request.headers.get('cookie')
// Include user-specific data in cache key for personalized content
const userContext = authHeader || cookieHeader || 'anonymous'
return `${request.method}:${url.pathname}:${url.search}:${userContext}`
}
function shouldBypassCache(request) {
const url = new URL(request.url)
// Bypass cache for specific conditions
return (
request.headers.get('cache-control') === 'no-cache' ||
request.headers.get('pragma') === 'no-cache' ||
url.searchParams.has('nocache') ||
url.pathname.startsWith('/api/') && request.method !== 'GET'
)
}
function getCacheDuration(request) {
const url = new URL(request.url)
// Different cache durations for different content
if (url.pathname.startsWith('/api/')) {
return 300 // 5 minutes for API
} else if (url.pathname.startsWith('/static/')) {
return 31536000 // 1 year for static assets
} else if (url.pathname.includes('/blog/')) {
return 3600 // 1 hour for blog posts
} else {
return 1800 // 30 minutes for regular pages
}
}
function generateCacheHeaders(request) {
const url = new URL(request.url)
const duration = getCacheDuration(request)
const headers = new Map()
if (url.pathname.startsWith('/api/')) {
headers.set('Cache-Control', `public, max-age=${duration}`)
headers.set('Vary', 'Authorization, Cookie')
} else if (url.pathname.startsWith('/static/')) {
headers.set('Cache-Control', `public, max-age=${duration}, immutable`)
headers.set('Vary', 'Accept-Encoding')
} else {
headers.set('Cache-Control', `public, max-age=${duration}, must-revalidate`)
headers.set('Vary', 'Accept-Encoding, Cookie')
}
return headers
}
// Mock cache functions (replace with real KV storage in production)
async function getFromCache(key) {
try {
// In production, use Netlify KV or similar
// const cached = await KV.get(key)
// return cached ? new Response(cached) : null
return null // Mock implementation
} catch (error) {
console.error('Cache get error:', error)
return null
}
}
async function setCache(key, response, duration) {
try {
// In production, use Netlify KV or similar
// const body = await response.text()
// await KV.put(key, body, { expirationTtl: duration })
console.log('Cached:', key, 'for', duration, 'seconds')
} catch (error) {
console.error('Cache set error:', error)
}
}Outils
Outils souvent utilisés avec cet exemple
Associé