Cos'è l'Interaction to Next Paint (INP)
L'Interaction to Next Paint (INP) è la metrica che dal marzo 2024 ha sostituito il First Input Delay (FID) come Core Web Vital, rappresentando un cambio paradigmatico nella misurazione della responsiveness delle pagine web.
A differenza del FID, che misurava solo il delay del primo input, l'INP valuta tutte le interazioni durante l'intero lifecycle della pagina, fornendo una visione completa della responsiveness percepita dall'utente.
Definizione Tecnica
L'INP misura il tempo tra l'input dell'utente e il successivo frame paint, considerando:
- Input Delay: Tempo tra l'evento e l'inizio dell'event handler
- Processing Time: Tempo di esecuzione dell'event handler
- Presentation Delay: Tempo tra la fine dell'handler e il paint del frame
Soglie INP per il 2025:
- Buono: ≤ 200ms
- Da migliorare: 200ms - 500ms
- Scarso: > 500ms
Differenze Fondamentali tra INP e FID
La transizione da FID a INP rappresenta un cambio di paradigma nell'approccio alla responsiveness:
Confronto FID vs INP
Aspetto | FID | INP |
---|---|---|
Scope misurazione | Solo primo input | Tutte le interazioni |
Tipo interazioni | Click, tap, keypress | Click, tap, keypress, scroll |
Fase misurata | Solo input delay | Input delay + processing + paint |
Rappresentatività | Limitata | Completa esperienza utente |
Implicazioni pratiche: Siti che performavano bene sul FID potrebbero mostrare problemi significativi con l'INP, specialmente quelli con interazioni complesse o JavaScript pesante.
Misurazione Accurata dell'INP
Implementazione via Web Vitals Library
// Implementazione INP con Web Vitals library
import { onINP } from 'web-vitals';
onINP((metric) => {
// Log dettagliato per debugging
console.log('INP Metric:', {
value: metric.value,
rating: metric.rating,
entries: metric.entries,
attribution: metric.attribution
});
// Invio dati a sistema di monitoring
sendToAnalytics({
metric_name: 'INP',
value: metric.value,
rating: metric.rating,
page_url: window.location.href,
timestamp: Date.now()
});
});
Attribution API per Debug Avanzato
// Configurazione attribution per debugging dettagliato
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
const attribution = metric.attribution;
// Identificazione elemento responsabile
const targetElement = attribution.interactionTarget;
const eventType = attribution.interactionType;
// Breakdown temporale dettagliato
const breakdown = {
inputDelay: attribution.inputDelay,
processingDuration: attribution.processingDuration,
presentationDelay: attribution.presentationDelay
};
// Log per identificazione problemi
console.warn('INP Attribution:', {
target: targetElement,
type: eventType,
breakdown: breakdown,
totalTime: metric.value
});
});
⚡ Pro Tip da Claudio
Per enterprise applications, implemento sempre un sistema di percentile tracking che monitora non solo il valore INP complessivo, ma anche il 75° e 95° percentile per identificare pattern di degrado performance.
Strategie di Ottimizzazione INP
1. Ottimizzazione Input Delay
L'input delay è causato principalmente dal main thread blocking. Strategie di mitigazione:
// Time slicing per prevenire main thread blocking
function timeSliceTask(task, chunkSize = 50) {
return new Promise((resolve) => {
let index = 0;
function processChunk() {
const start = Date.now();
while (index < task.length && Date.now() - start < chunkSize) {
task[index]();
index++;
}
if (index < task.length) {
// Yield control al browser
setTimeout(processChunk, 0);
} else {
resolve();
}
}
processChunk();
});
}
2. Riduzione Processing Time
Ottimizzazione degli event handler con debouncing intelligente e lazy execution:
// Debouncing avanzato per eventi frequenti
class SmartDebouncer {
constructor(func, delay = 100, maxWait = 300) {
this.func = func;
this.delay = delay;
this.maxWait = maxWait;
this.timeoutId = null;
this.maxTimeoutId = null;
this.lastCallTime = 0;
}
execute(...args) {
const now = Date.now();
// Clear previous timeouts
if (this.timeoutId) clearTimeout(this.timeoutId);
if (this.maxTimeoutId) clearTimeout(this.maxTimeoutId);
// Set max wait timeout if first call
if (!this.lastCallTime) {
this.maxTimeoutId = setTimeout(() => {
this.func.apply(this, args);
this.reset();
}, this.maxWait);
}
// Set debounce timeout
this.timeoutId = setTimeout(() => {
this.func.apply(this, args);
this.reset();
}, this.delay);
this.lastCallTime = now;
}
reset() {
this.timeoutId = null;
this.maxTimeoutId = null;
this.lastCallTime = 0;
}
}
// Implementazione per scroll handler
const optimizedScrollHandler = new SmartDebouncer(
handleScroll,
16, // ~60fps
100 // max wait
);
window.addEventListener('scroll', (e) => {
optimizedScrollHandler.execute(e);
});
3. Minimizzazione Presentation Delay
Ottimizzazione del rendering path con batching degli aggiornamenti DOM:
// Batching intelligente degli aggiornamenti DOM
class DOMBatcher {
constructor() {
this.pendingUpdates = new Set();
this.isScheduled = false;
}
scheduleUpdate(updateFn) {
this.pendingUpdates.add(updateFn);
if (!this.isScheduled) {
this.isScheduled = true;
// Usa scheduler API se disponibile
if ('scheduler' in window && 'postTask' in scheduler) {
scheduler.postTask(() => this.flushUpdates(), {
priority: 'user-blocking'
});
} else {
// Fallback a requestAnimationFrame
requestAnimationFrame(() => this.flushUpdates());
}
}
}
flushUpdates() {
// Batch tutti gli aggiornamenti DOM
this.pendingUpdates.forEach(updateFn => {
try {
updateFn();
} catch (error) {
console.error('DOM update failed:', error);
}
});
this.pendingUpdates.clear();
this.isScheduled = false;
}
}
// Utilizzo globale del batcher
const domBatcher = new DOMBatcher();
// Esempio di utilizzo
function updateUI(data) {
domBatcher.scheduleUpdate(() => {
document.getElementById('content').textContent = data.content;
document.getElementById('status').className = data.status;
});
}
Debugging Avanzato con Chrome DevTools
Per il debugging INP uso un approccio sistematico che combina Performance Timeline, Long Task API e profiling personalizzato.
Setup Debugging Environment
// Configurazione ambiente debugging INP
class INPDebugger {
constructor() {
this.interactions = new Map();
this.longTasks = [];
this.setupObservers();
}
setupObservers() {
// Observer per Long Tasks
if ('PerformanceObserver' in window) {
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.longTasks.push({
startTime: entry.startTime,
duration: entry.duration,
name: entry.name,
attribution: entry.attribution
});
}
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
}
// Observer per Event Timing
const eventObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.trackInteraction(entry);
}
});
eventObserver.observe({
entryTypes: ['event'],
buffered: true
});
}
trackInteraction(entry) {
const interactionId = entry.interactionId;
if (!interactionId) return;
if (!this.interactions.has(interactionId)) {
this.interactions.set(interactionId, []);
}
this.interactions.get(interactionId).push({
type: entry.name,
startTime: entry.startTime,
processingStart: entry.processingStart,
processingEnd: entry.processingEnd,
duration: entry.duration,
target: entry.target
});
}
analyzeINPBottlenecks() {
const worstInteractions = Array.from(this.interactions.entries())
.map(([id, events]) => ({
id,
duration: Math.max(...events.map(e => e.duration)),
events
}))
.sort((a, b) => b.duration - a.duration)
.slice(0, 5);
console.group('🔍 INP Analysis');
worstInteractions.forEach(interaction => {
console.log(`Interaction ${interaction.id}: ${interaction.duration}ms`);
console.table(interaction.events);
});
console.groupEnd();
return worstInteractions;
}
}
// Inizializzazione debugger
const inpDebugger = new INPDebugger();
// Analisi periodica (solo in development)
if (process.env.NODE_ENV === 'development') {
setInterval(() => {
inpDebugger.analyzeINPBottlenecks();
}, 30000);
}
🛠️ Debugging Workflow Claudio
Il mio workflow di debugging INP prevede sempre questi step: 1) Registrazione dettagliata via Performance API, 2) Correlazione con Long Tasks, 3) Analisi attribution, 4) Profiling targeted con DevTools, 5) A/B testing delle ottimizzazioni.
Monitoring in Produzione
Per il monitoring INP in produzione, implemento sempre un sistema di Real User Monitoring (RUM) che cattura non solo le metriche, ma anche il contesto dell'interazione.
// Sistema RUM completo per INP monitoring
class INPMonitor {
constructor(config = {}) {
this.config = {
sampleRate: config.sampleRate || 0.1,
endpoint: config.endpoint || '/api/metrics',
bufferSize: config.bufferSize || 100,
...config
};
this.buffer = [];
this.sessionId = this.generateSessionId();
this.setupMonitoring();
}
setupMonitoring() {
import('web-vitals/attribution').then(({ onINP }) => {
onINP((metric) => {
if (Math.random() > this.config.sampleRate) return;
const payload = {
sessionId: this.sessionId,
timestamp: Date.now(),
url: location.href,
metric: {
name: 'INP',
value: metric.value,
rating: metric.rating
},
attribution: {
interactionTarget: this.getElementSelector(
metric.attribution.interactionTarget
),
interactionType: metric.attribution.interactionType,
inputDelay: metric.attribution.inputDelay,
processingDuration: metric.attribution.processingDuration,
presentationDelay: metric.attribution.presentationDelay
},
context: this.getPageContext(),
userAgent: navigator.userAgent,
connection: this.getConnectionInfo()
};
this.buffer.push(payload);
if (this.buffer.length >= this.config.bufferSize) {
this.flush();
}
});
});
// Flush periodico e su page unload
setInterval(() => this.flush(), 30000);
addEventListener('beforeunload', () => this.flush());
}
getElementSelector(element) {
if (!element) return null;
// Genera selector CSS univoco
const path = [];
while (element && element !== document.body) {
let selector = element.tagName.toLowerCase();
if (element.id) {
selector += `#${element.id}`;
path.unshift(selector);
break;
}
if (element.className) {
selector += `.${element.className.split(' ').join('.')}`;
}
path.unshift(selector);
element = element.parentElement;
}
return path.join(' > ');
}
getPageContext() {
return {
viewportSize: {
width: window.innerWidth,
height: window.innerHeight
},
scrollPosition: {
x: window.scrollX,
y: window.scrollY
},
documentSize: {
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight
},
visibilityState: document.visibilityState,
activeElement: this.getElementSelector(document.activeElement)
};
}
getConnectionInfo() {
if (!navigator.connection) return null;
return {
effectiveType: navigator.connection.effectiveType,
rtt: navigator.connection.rtt,
downlink: navigator.connection.downlink,
saveData: navigator.connection.saveData
};
}
flush() {
if (this.buffer.length === 0) return;
const data = [...this.buffer];
this.buffer = [];
// Invio con sendBeacon se disponibile
if (navigator.sendBeacon) {
navigator.sendBeacon(
this.config.endpoint,
JSON.stringify(data)
);
} else {
// Fallback con fetch keep-alive
fetch(this.config.endpoint, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
keepalive: true
}).catch(error => {
console.warn('INP monitoring failed:', error);
});
}
}
generateSessionId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
// Inizializzazione monitoring
const inpMonitor = new INPMonitor({
endpoint: '/api/rum/inp',
sampleRate: 0.1 // 10% sampling
});
Implementazione Enterprise
Per implementazioni enterprise, la strategia INP deve considerare scalabilità, governance e integration con existing performance budgets.
Performance Budget Integration
// Sistema di performance budget per INP
class INPBudgetGuard {
constructor(config) {
this.budgets = {
inp: {
warning: config.inp?.warning || 150,
error: config.inp?.error || 250
}
};
this.violations = [];
this.setupGuards();
}
setupGuards() {
import('web-vitals').then(({ onINP }) => {
onINP((metric) => {
const violation = this.checkBudget(metric);
if (violation) {
this.handleViolation(violation);
}
});
});
}
checkBudget(metric) {
const { value, rating } = metric;
const budget = this.budgets.inp;
if (value > budget.error) {
return {
type: 'error',
metric: 'INP',
value,
threshold: budget.error,
severity: 'high'
};
} else if (value > budget.warning) {
return {
type: 'warning',
metric: 'INP',
value,
threshold: budget.warning,
severity: 'medium'
};
}
return null;
}
handleViolation(violation) {
this.violations.push({
...violation,
timestamp: Date.now(),
url: location.href
});
// Alert in development
if (process.env.NODE_ENV === 'development') {
console.warn(`⚠️ Performance Budget Violation`, violation);
}
// Report to monitoring system
this.reportViolation(violation);
}
reportViolation(violation) {
// Integration with enterprise monitoring
if (window.performanceMonitoring) {
window.performanceMonitoring.reportViolation(violation);
}
// Custom reporting logic
fetch('/api/performance/violations', {
method: 'POST',
body: JSON.stringify(violation),
headers: { 'Content-Type': 'application/json' }
});
}
}
// CI/CD Integration
class INPCIIntegration {
static generateReport(violations) {
const summary = violations.reduce((acc, v) => {
acc[v.severity] = (acc[v.severity] || 0) + 1;
return acc;
}, {});
return {
status: summary.high > 0 ? 'failed' : 'passed',
summary,
details: violations,
recommendations: this.generateRecommendations(violations)
};
}
static generateRecommendations(violations) {
const recommendations = [];
violations.forEach(v => {
if (v.value > 300) {
recommendations.push(
'Consider implementing time-slicing for heavy computations'
);
}
if (v.value > 200) {
recommendations.push(
'Review event handlers for optimization opportunities'
);
}
});
return [...new Set(recommendations)];
}
}
🏢 Enterprise Best Practice
In contesti enterprise, implemento sempre un three-tier approach: 1) Real-time monitoring per detection immediata, 2) Synthetic testing per regression prevention, 3) Performance budgets integration nel CI/CD pipeline per governance automatica.
Conclusioni
L'ottimizzazione INP per il 2025 richiede un approccio sistematico e data-driven. Le strategie presentate in questo articolo sono il risultato di implementazioni reali su siti enterprise con milioni di utenti mensili.
Key takeaways:
- INP richiede ottimizzazione di tutto il pipeline di interazione, non solo dell'input delay
- Il debugging avanzato tramite Attribution API è fondamentale per identificazioni precise
- Il monitoring in produzione deve catturare contesto e correlation data
- Le implementazioni enterprise necessitano di governance automatica e integration CI/CD
La mia esperienza diretta nell'implementazione di questi sistemi su progetti enterprise dimostra che un approccio structured porta a miglioramenti INP del 40-60% nella maggior parte dei casi.