Lazy-load tu wallet SDK para arreglar INP y pasar Core Web Vitals
Los SDKs de wallet-connect son el mayor asesino de INP en sitios Web3. Cargarlos eager en cada página cuesta 200-400KB de JS y 200-400ms de TBT. Aquí está cómo lazy-loadearlos en 30 minutos.
Por qué importa esto
- →Wallet SDK eager-load is the most common Web3 CWV killer across 200+ TG3 audits.
- →Google's 2026 INP target is under 200ms. Eager-loaded RainbowKit alone adds 250-380ms TBT on mid-range mobile.
- →Magic Square (cliente de TG3) went from 380ms INP to under 200ms in 14 days using exactly this pattern.
- →1.6x organic traffic lift in 60 days from CWV improvements alone on Magic Square. The fix is 10 lines of code.
Estado anterior (cómo se ve lo malo)
// pages/_app.tsx (Next.js example)
import { WagmiConfig, createConfig } from 'wagmi';
import { ConnectKitProvider, getDefaultConfig } from 'connectkit';
// SDK loads on every page mount, even marketing pages
const config = createConfig(getDefaultConfig({
appName: 'My dApp',
walletConnectProjectId: 'xxx',
}));
export default function App({ Component, pageProps }) {
return (
<WagmiConfig config={config}>
<ConnectKitProvider>
<Component {...pageProps} />
</ConnectKitProvider>
</WagmiConfig>
);
}
Paso a paso
Paso 1: Mide tu INP base
Abre Chrome DevTools, ve a la pestaña Performance, throttlea a "Slow 4G" y 4x CPU slowdown (simula Android de gama media). Recarga tu homepage y captura un trace de Performance. Anota el Total Blocking Time (TBT) e Interaction to Next Paint (INP). Cualquier cosa arriba de 200ms INP falla CWV.
Paso 2: Identifica el wallet SDK en tu bundle
Corre un bundle analyzer. Para Next.js: ANALYZE=true npm run build. Para Vite: vite-bundle-visualizer. Busca @rainbow-me/rainbowkit, @web3modal/wagmi, wagmi, ethers, viem. Estos típicamente representan 200-500KB de JS en sitios Web3. Ese es tu objetivo para lazy-loading.
# Next.js bundle analyzer
npm i -D @next/bundle-analyzer
# Then add to next.config.js and run:
ANALYZE=true npm run build
Paso 3: Marca el botón de connect con un atributo data
Añade data-connect-wallet a cada botón <Connect Wallet> a través de tu sitio. Este es el trigger que carga el SDK. No uses handlers onClick porque necesitamos detectar el click antes de que el handler corra (el handler no existe todavía en ese punto).
<button data-connect-wallet className="...">Connect Wallet</button>
Paso 4: Implementa el wrapper de dynamic import
Usa el dynamic() de Next.js con ssr:false para lazy-loadear el wallet provider. El componente wrapper renderiza children directamente hasta que un botón connect es clickeado, luego carga el SDK y envuelve los children en el provider real. Usa el código del "Estado posterior" como tu plantilla.
Paso 5: Mueve la inicialización del SDK al componente interno
Todo el código de config de wagmi/RainbowKit/Web3Modal va en el componente interno (cargado bajo demanda). El wrapper externo solo escucha el trigger. De esta manera, el código de config del SDK solo corre cuando se necesita.
Paso 6: Prueba el flujo de connect end-to-end
Haz click en "Connect Wallet" en tu homepage. Verifica: (1) el SDK carga (la pestaña Network muestra los chunks), (2) el modal abre, (3) la conexión de wallet completa, (4) las acciones post-connect funcionan normalmente. La experiencia hacia el usuario debería ser idéntica a antes.
Paso 7: Re-mide el INP y confirma la mejora
Vuelve a correr el trace de Performance de Chrome DevTools del Paso 1. INP debería caer significativamente (típico: 380ms → 180ms). Corre PageSpeed Insights para la versión de field-data (la data de CrUX toma 28 días para actualizar completamente pero la data de lab actualiza inmediatamente). Documenta el antes/después para tu equipo.
FREE WEB3 AUDIT
Mira dónde aplica este playbook en tu sitio.
Corre una auditoría Crawlux gratis antes de empezar el playbook. Te dice qué correcciones son más urgentes.
Primera auditoría gratis · Sin registro · 60 segundos · Full PDF report
Estado posterior (cómo se ve lo bueno)
// components/WalletProvider.tsx - dynamic import
import dynamic from 'next/dynamic';
import { useState } from 'react';
const WalletProviderInner = dynamic(
() => import('./WalletProviderInner'),
{ ssr: false, loading: () => null }
);
export function WalletProvider({ children }) {
const [walletNeeded, setWalletNeeded] = useState(false);
// Listen for the connect button click
if (!walletNeeded) {
return (
<div onClick={(e) => {
if (e.target.closest('[data-connect-wallet]')) {
setWalletNeeded(true);
}
}}>
{children}
</div>
);
}
return <WalletProviderInner>{children}</WalletProviderInner>;
}
// components/WalletProviderInner.tsx - actual SDK
import { WagmiConfig, createConfig } from 'wagmi';
import { ConnectKitProvider, getDefaultConfig } from 'connectkit';
const config = createConfig(getDefaultConfig({
appName: 'My dApp',
walletConnectProjectId: 'xxx',
}));
export default function WalletProviderInner({ children }) {
return (
<WagmiConfig config={config}>
<ConnectKitProvider>{children}</ConnectKitProvider>
</WagmiConfig>
);
}
Cómo validar la corrección
- ✓PageSpeed Insights lab data: INP under 200ms.
- ✓Bundle analyzer: wallet SDK chunks not present in initial JS payload (should be in async chunks).
- ✓Network tab on first page load: zero requests to wallet SDK files.
- ✓Network tab after clicking connect: SDK chunks load, modal opens within 500ms.
- ✓After 28 days: PageSpeed Insights field data (CrUX) shows INP improvement at the 75th percentile.
Errores comunes
Pitfall
Olvidar la dependencia de contexto de wagmi
Algunos componentes aguas abajo pueden usar useAccount() de wagmi esperando que el provider exista. Envuelve esos con renderizado condicional o usa un valor por defecto cuando el SDK no esté cargado todavía.
Pitfall
Errores SSR de dynamic import
Siempre usa { ssr: false } en las opciones de dynamic(). Los SDKs de wallet asumen globales del navegador (window, localStorage) y se caen durante SSR.
Pitfall
Auto-connect en reload de página no funciona
Si los usuarios tenían auto-connect habilitado, esperan ser auto-conectados al recargar. Solución: verifica la cookie/localStorage de conexión persistida en el mount inicial y dispara setWalletNeeded(true) si se encuentra. De lo contrario auto-connect se rompe.
Pitfall
Botón connect en una ruta diferente disparando carga demasiado temprano
Si tu sitio tiene el botón connect en un header global y un usuario aterriza en /blog/some-post/, el SDK carga al click pero el usuario ya está profundo en el contenido. Eso está bien en realidad; el objetivo es evitar cargar en el mount inicial de página, no nunca cargar.
Pitfall
Problemas de tree-shaking con providers de wagmi
wagmi 2.x ha sido más agresivo con tree-shaking. Algunos imports disparan carga completa del SDK. Usa el bundle analyzer para verificar que tu chunk dinámico no incluya accidentalmente el SDK entero en el main bundle.
Si algo se rompe: rollback
Revierte WalletProvider.tsx a la versión eager-load. La wallet funciona exactamente como antes en minutos. INP regresa a la línea base. Considera este rollback solo si el patrón dynamic-load rompe flujos de usuario; la mejora de performance vale esfuerzo significativo para corregir hacia adelante.
Corre una auditoría Crawlux gratis sobre esta corrección
Crawlux valida las correcciones de schema, técnicas y AEO de este playbook automáticamente. Plan gratis en un dominio.
Ejecutar auditoría gratuita →Preguntas frecuentes
¿Funciona esto con Vite o Vue u otros frameworks?
Sí. El patrón es universal: dynamic import detrás de un trigger de acción de usuario. Vite tiene React.lazy() con Suspense. Vue tiene defineAsyncComponent. Misma idea: solo carga el wallet SDK bajo demanda, no en el mount inicial.
¿Qué si necesito conexión de wallet al inicio de la app (ej., dashboard SaaS)?
Entonces el lazy-loading no es el patrón correcto; eager-load es correcto para rutas de app. Aplica este patrón solo a páginas de marketing y páginas de contenido donde la conexión de wallet es opcional. Usa carga basada en ruta: las rutas de marketing lazy-load, las rutas de app eager-load.
¿Esto romperá los deep links de WalletConnect?
No. Las URIs de WalletConnect se generan cuando el modal abre. Mientras el SDK cargue cuando el modal necesita renderizar, los deep links funcionan normalmente.
¿Cómo manejo el provider inyectado de metamask?
El window.ethereum inyectado de MetaMask está disponible sin el SDK. Si quieres detectar la presencia de MetaMask en la carga inicial (sin cargar el SDK completo), verifica window.ethereum?.isMetaMask. Lazy-load el SDK solo cuando el usuario hace click en connect.
¿Esto afecta el SEO?
Positivamente. Las páginas más rápidas rankean mejor. INP es una señal de ranking confirmada en el algoritmo de Google de 2026. El SDK no es contenido SEO-relevante (es código funcional), así que removerlo de la carga inicial no daña nada que Google indexe.
Playbooks relacionados
Guías pilares
Módulos de auditoría
RUN YOUR FIRST AUDIT
Corre el playbook contra una auditoría real.
Recibe un reporte de auditoría Crawlux gratis y úsalo como línea base para el trabajo en este playbook.
Primera auditoría gratis · Sin registro · 60 segundos · Full PDF report
