Rendimiento: medir antes de optimizar
En Ionic (Angular/React/Vue), el rendimiento tiene dos caras: real (tiempos de CPU, red, memoria) y percibido (qué tan rápido “se siente” la app). Una pantalla puede tardar 2s en estar lista, pero si muestras un esqueleto y evitas bloqueos del hilo principal, el usuario percibe fluidez.
Antes de tocar código, define una métrica simple por pantalla: tiempo hasta primer contenido útil (skeleton/placeholder), tiempo hasta interacción (botones responden) y suavidad de scroll (listas sin tirones). Luego optimiza lo que más impacta: carga diferida, listas, imágenes, estilos y re-renderizados.
Carga diferida (lazy loading) y división de código
Objetivo
Reducir el tamaño del bundle inicial y cargar pantallas/funcionalidades solo cuando se necesitan.
Guía práctica (Angular)
- Verifica rutas lazy: cada página/feature debe estar en su módulo y cargarse con
loadChildren. - Evita importar módulos pesados en el módulo raíz si solo se usan en una sección.
// app-routing.module.ts (ejemplo típico de lazy loading en Ionic Angular)
const routes: Routes = [
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsPageModule)
}
];Guía práctica (React)
- Usa
React.lazy+Suspensepara cargar vistas bajo demanda.
const Products = React.lazy(() => import('./pages/Products'));
<Suspense fallback={<IonSpinner />}>
<Route path="/products" component={Products} exact />
</Suspense>Guía práctica (Vue)
- Define rutas con import dinámico.
{
path: '/products',
component: () => import('@/views/Products.vue')
}Reducir re-renderizados y trabajo innecesario
Concepto
En apps híbridas, el hilo principal (UI) es un recurso crítico. Re-renderizados frecuentes, cálculos pesados en cada cambio o bindings excesivos se traducen en scroll con tirones, taps que “no entran” y animaciones poco fluidas.
Angular: OnPush y trackBy
Si un componente recibe datos inmutables (nuevas referencias al cambiar), ChangeDetectionStrategy.OnPush reduce detecciones de cambios.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
@Input() product!: { id: string; name: string; price: number };
}En listas, usa trackBy para evitar recrear DOM cuando el array cambia.
<ion-item *ngFor="let p of products; trackBy: trackById">
{{ p.name }}
</ion-item>trackById(_: number, item: { id: string }) {
return item.id;
}React: memoización y claves estables
- Usa
React.memopara componentes puros. - Usa
useMemo/useCallbackcuando pases funciones/objetos a hijos y eso dispare renders. - En listas, usa
keyestable (id), no el índice.
const ProductRow = React.memo(function ProductRow({ product, onOpen }) {
return (
<IonItem button onClick={() => onOpen(product.id)}>
<IonLabel>{product.name}</IonLabel>
</IonItem>
);
});
const onOpen = useCallback((id) => {
// navegación
}, []);
return products.map(p => (
<ProductRow key={p.id} product={p} onOpen={onOpen} />
));Vue: computed, v-memo y keys
- Prefiere
computedpara derivar datos y evitar recalcular en cada render. - Usa
:keyestable env-for. - En Vue 3,
v-memopuede ayudar en subárboles estáticos (úsalo con criterio).
<ion-item v-for="p in products" :key="p.id">
<ion-label>{{ p.name }}</ion-label>
</ion-item>Optimización de listas: virtualización y patrones de UI
Concepto
Las listas largas son el caso más común de problemas de rendimiento. Renderizar cientos de nodos (items con imágenes, botones, badges) puede saturar el DOM y el layout.
Estrategias recomendadas
- Paginación/infinite scroll: carga por bloques y evita traer todo de golpe.
- Virtualización: renderiza solo lo visible (cuando aplique).
- Items simples: reduce profundidad de DOM dentro de cada fila.
- Evita cálculos en plantilla: no hagas
formatPrice(p.price)en cada render si se recalcula; precalcula o memoiza.
Guía práctica: Infinite Scroll (patrón general)
Usa un tamaño de página fijo, agrega al array y marca el evento como completado. Mantén un flag hasMore para detener la carga.
// Pseudocódigo (aplicable a frameworks con pequeñas variaciones)
page = 0;
pageSize = 20;
hasMore = true;
items = [];
async function loadMore(event) {
const next = await fetchPage(page, pageSize);
items = items.concat(next);
page++;
if (next.length < pageSize) hasMore = false;
event.target.complete();
}Imágenes: peso, tamaño y carga eficiente
Concepto
Las imágenes suelen ser el mayor costo de red y memoria. Optimizar imágenes mejora el tiempo de carga y reduce cierres por falta de memoria en dispositivos modestos.
Checklist práctico
- Sirve el tamaño correcto: no cargues una imagen de 2000px para mostrarla a 200px.
- Comprime: usa formatos modernos (WebP/AVIF si tu pipeline lo permite) y calidad adecuada.
- Lazy load: carga diferida en listas y pantallas con muchas imágenes.
- Evita reflow: reserva espacio (ancho/alto) para que el layout no “salte”.
Ejemplo: IonImg y carga diferida
ion-img está pensado para carga eficiente y manejo de imágenes en Ionic.
<ion-img
[src]="product.thumbnailUrl"
alt="Foto del producto"
></ion-img>Si usas <img>, activa lazy loading y define dimensiones.
<img
[src]="product.thumbnailUrl"
alt="Foto del producto"
loading="lazy"
width="80"
height="80"
/>Estilos y animaciones: menos costo de layout y pintura
Concepto
Al hacer scroll o animar, lo caro suele ser recalcular layout y repintar. En móviles, pequeños excesos de sombras, blur y layouts complejos se notan.
Buenas prácticas
- Prefiere transform/opacity para animaciones (suelen ser más baratas que cambiar top/left/width).
- Reduce sombras pesadas y efectos de blur en listas largas.
- Evita selectores CSS muy costosos (profundos, con muchos descendientes) en pantallas con muchos nodos.
- Usa variables CSS de Ionic para tematizar sin sobreescribir demasiadas reglas.
/* Ejemplo: animar con transform en lugar de top */
.card {
transition: transform 200ms ease;
}
.card--pressed {
transform: scale(0.98);
}Memoización: cuándo aplica y cuándo no
Idea clave
Memoizar no es “magia”: reduce trabajo repetido a cambio de memoria y complejidad. Úsalo cuando tengas cálculos costosos o renders frecuentes por cambios no relevantes.
Casos típicos
- Listas: formateo de datos, filtrados y ordenamientos (usar
useMemo/computed). - Componentes puros: filas de lista que solo cambian si cambia el item.
- Evitar recrear handlers:
useCallbacken React cuando se pasa a hijos memoizados.
Antipatrón común
Memoizar todo sin medir: si el cálculo es trivial, puedes empeorar rendimiento por overhead y dificultar el mantenimiento.
Accesibilidad móvil (a11y): criterios prácticos
Objetivo
Que la app sea usable con lector de pantalla, con tamaños de texto grandes, con buen contraste y con interacción táctil confiable.
Checklist esencial en Ionic
- Etiquetas accesibles: todo control interactivo debe tener nombre (texto visible o
aria-label). - Orden de enfoque: navegación coherente con teclado/lector (especialmente en modales).
- Tamaño táctil: objetivos de toque cómodos (botones pequeños generan errores).
- Contraste: texto legible sobre fondos; evita depender solo del color para indicar estado.
- Estados: usa
aria-busy,aria-liveo mensajes visibles para cargas/errores.
Ejemplos
<ion-button aria-label="Abrir carrito">
<ion-icon name="cart" aria-hidden="true"></ion-icon>
</ion-button><ion-item>
<ion-label>Notificaciones</ion-label>
<ion-toggle aria-label="Activar notificaciones"></ion-toggle>
</ion-item>Pruebas manuales de interacción táctil
Haz una pasada rápida por pantalla con este guion:
- Tap: ¿los botones responden a la primera? ¿hay doble tap accidental?
- Scroll: ¿se puede hacer scroll sin activar items? (revisa
buttonenion-itemy áreas táctiles). - Gestos: ¿swipe/back funciona sin conflictos con scroll horizontal?
- Teclado: al abrir inputs, ¿el contenido queda oculto? ¿se puede enviar/cerrar sin perder contexto?
- Rotación y tamaños de texto: ¿se rompe el layout con texto grande?
Depuración: herramientas y técnicas que sí resuelven problemas
Logs estructurados (útiles en dispositivo)
Evita console.log sueltos. Usa logs con contexto: módulo, acción, ids, duración y resultado. Esto acelera el diagnóstico de fallos intermitentes.
// Ejemplo simple de logger estructurado
function logInfo(scope: string, message: string, data?: any) {
console.info(JSON.stringify({ level: 'info', scope, message, data, ts: Date.now() }));
}
async function loadProducts() {
const t0 = performance.now();
logInfo('products', 'load_start');
try {
const res = await fetch('/api/products');
logInfo('products', 'load_response', { status: res.status });
} finally {
logInfo('products', 'load_end', { ms: Math.round(performance.now() - t0) });
}
}Inspección de red
Problemas típicos de “la app va lenta” son realmente de red: endpoints lentos, payloads enormes, falta de cache o demasiadas llamadas.
- Qué revisar: tiempo total, TTFB, tamaño de respuesta, número de requests por pantalla.
- Señales de alerta: respuestas > 1MB en listas, múltiples requests en cascada, reintentos silenciosos.
- Acciones: paginar, comprimir, cachear, evitar duplicados (deduplicación de llamadas concurrentes).
Profiling básico (CPU y frames)
Cuando hay tirones, busca tareas largas en el hilo principal (JS) y repaints costosos.
- Qué medir: al abrir una pantalla, al hacer scroll en lista, al abrir modal.
- Qué suele aparecer: formateos en bucle, filtros/ordenamientos en cada render, imágenes grandes, demasiados nodos.
- Acción rápida: mover cálculos fuera del render, memoizar derivaciones, simplificar item de lista, paginar.
Resolución de problemas típicos
Pantalla en blanco (white screen)
Suele ser un error de runtime (JS) o un problema de build/rutas. Procedimiento recomendado:
- Reproduce con logs: conecta el dispositivo/emulador y revisa la consola (errores rojos, stack trace).
- Verifica rutas: una ruta mal configurada o un import dinámico fallido puede dejar la app sin render.
- Revisa assets: 404 de
main.js,polyfillso archivos de estilos indica problema de despliegue/base href. - Desactiva temporalmente optimizaciones: source maps y build no minificado para obtener trazas legibles.
- Aísla el componente: comenta secciones hasta encontrar el render que rompe (por ejemplo, acceso a propiedad
undefined).
Fallos de build
En proyectos Ionic, los fallos de build suelen venir de dependencias, versiones o configuración de plataforma.
- Errores TypeScript: revisa tipos y APIs; no fuerces
anypara “pasar” el build si el error es real. - Dependencias: si aparece un conflicto, alinea versiones de paquetes relacionados (Ionic, framework, plugins).
- Cache: limpia artefactos cuando el error es “fantasma” (builds inconsistentes).
// Comandos típicos (ajusta a tu stack)
// npm ci (instalación limpia)
// eliminar node_modules y lock si es necesario
// limpiar plataformas nativas si aplica (android/ios) y reconstruirProblemas de permisos (cámara, ubicación, archivos)
Cuando algo funciona en navegador pero falla en dispositivo, suele ser permisos o configuración nativa.
- Confirma el estado del permiso: antes de ejecutar la acción, consulta si está concedido/denegado.
- Solicita permiso en el momento correcto: explica por qué se necesita (mejora aceptación).
- Revisa configuración de plataforma: en iOS, descripciones de uso; en Android, permisos en manifest y cambios por versión.
- Maneja denegación: muestra UI para reintentar o abrir ajustes.
// Pseudocódigo de flujo de permisos
status = await permissions.check('camera');
if (status !== 'granted') {
status = await permissions.request('camera');
}
if (status !== 'granted') {
showMessage('Necesitamos permiso de cámara para continuar.');
return;
}
openCamera();Mini plan de optimización por pantalla (paso a paso)
Úsalo como rutina repetible para cualquier vista que se sienta lenta:
- Define el escenario: “abrir pantalla X desde home”, “scroll hasta item 100”, “buscar y filtrar”.
- Instrumenta: agrega logs de inicio/fin con duración (
performance.now()) y cuenta de items renderizados. - Reduce red: pagina, cachea, evita duplicados, comprime payload.
- Optimiza lista: infinite scroll/virtualización,
trackBy/keyestable, item más simple. - Controla renders: OnPush / memo / computed; evita crear objetos/funciones en plantilla/render.
- Optimiza imágenes: tamaño correcto, lazy load, reserva dimensiones.
- Revalida accesibilidad: labels, foco, contraste, targets táctiles.
- Repite medición: compara antes/después con la misma interacción.