🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
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
💻 Middleware Edge de Base javascript
🟢 simple
⭐⭐
Patterns essentiels de middleware edge pour le traitement des requêtes, modification des réponses et routage de base
⏱️ 15 min
🏷️ netlify, edge functions, middleware
Prerequisites:
JavaScript ES6+, HTTP basics, Netlify edge functions
// Basic Edge Middleware Examples
// File: edge-functions/middleware.js
// 1. Request logging and analytics middleware
export default 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
export default 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
export default 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
export default 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 javascript
🟡 intermediate
⭐⭐⭐
Routage basé sur la géolocalisation, localisation de contenu et fonctionnalité spécifique à la région
⏱️ 25 min
🏷️ netlify, edge functions, geolocation
Prerequisites:
JavaScript, Geolocation APIs, Content localization
// Geolocation and Localization Examples
// File: edge-functions/geolocation.js
export default 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
export default 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
export default 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 javascript
🔴 complex
⭐⭐⭐⭐
Système complet d'authentification avec validation JWT, gestion de session et contrôle d'accès basé sur les rôles
⏱️ 35 min
🏷️ netlify, edge functions, security, auth
Prerequisites:
JWT, Authentication concepts, Cookie management, Role-based access control
// Authentication and Authorization Examples
// File: edge-functions/auth.js
export default 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
export default 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
export default 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 javascript
🔴 complex
⭐⭐⭐⭐⭐
Optimisation avancée des performances avec caching, compression, intégration CDN et optimisation des réponses
⏱️ 45 min
🏷️ netlify, edge functions, performance, optimization
Prerequisites:
Performance optimization, Caching strategies, Web vitals, Service workers
// Performance Optimization Examples
// File: edge-functions/performance.js
export default 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
export default 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)
}
}