Autenticación en React, carga de datos y errores: Axios, React Router, Redux, TanStack Query
Diez errores en React relacionados con la autenticación, la carga de datos y los errores, resueltos mediante interceptores de Axios, cargadores de React Router, middleware de Redux, TanStack Query y límites de errores.
Fuentes: Stop Polluting Your Components: The React ‘Middleware’ Guide (Axios, Routers, and State)
En el backend, nadie verifica la autenticación dentro de cada manejador de ruta ni inserta registros de actividad en cada controlador; esa tarea se realiza mediante middleware, que se instala una sola vez y se aplica a cada solicitud. Los proyectos basados en React suelen perder esta disciplina. Los componentes terminan encargándose ellos mismos de la inyección de tokens, las redirecciones 401, el registro de errores y la obtención de datos, lo que genera código genérico, un comportamiento inconsistente y errores que solo aparecen cuando un usuario hace clic en las opciones en un orden específico. Una aplicación React tiene su propio ciclo de vida de solicitudes, con llamadas a API, transiciones de ruta y envíos de estado que fluyen a través de capas a las que se puede acceder mediante hooks. Esta guía aborda diez errores comunes en interceptores de Axios, cargadores de React Router, bibliotecas de estado, límites de errores y middleware de Next.js, y muestra dónde debería residir cada función específica.
Consideraciones de transporte: autenticación e interceptores de Axios
Error 1: manejo de respuestas 401 en cada componente
El manejo de la autenticación es la causa más frecuente de desorden en los componentes. Cada función fetch cuenta con su propio bloque catch que verifica si hay un error 401, limpia el almacenamiento y redirige:
// ❌ Bad: Repeated in every component
async function fetchDashboard() {
try {
const res = await axios.get('/api/dashboard');
return res.data;
} catch (error) {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
throw error;
}
}
async function fetchProfile() {
try {
const res = await axios.get('/api/profile');
return res.data;
} catch (error) {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
throw error;
}
}
Más allá de la repetición, este enfoque es frágil. Pasar del almacenamiento local a las cookies implica editar cada lugar donde se realiza la llamada. Agregar la rotación del token de actualización antes de la redirección significa modificar nuevamente cada archivo. Además, un solo control olvidado deja la aplicación en un estado de sesión parcialmente cerrada.
La solución es un interceptor de respuestas de Axios en una instancia de cliente compartida, que recibe cada respuesta antes que cualquier componente. La versión que se muestra a continuación también adjunta el token al finalizar la operación, y ante un error 401 intenta actualizarlo una vez (protegido por una bandera _retry para evitar bucles), vuelve a enviar la solicitud original con el nuevo token, y solo recurre a borrar el almacenamiento y redirigir si la actualización falla:
// api/client.js
import axios from 'axios';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
// Request interceptor: attach token
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor: handle global errors
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// Attempt token refresh
const refreshToken = localStorage.getItem('refreshToken');
const { data } = await axios.post('/auth/refresh', { refreshToken });
localStorage.setItem('token', data.accessToken);
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
return api(originalRequest);
} catch (refreshError) {
// Refresh failed, redirect to login
localStorage.removeItem('token');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;
Los componentes ahora solo importan api y lo llaman, sin tener en cuenta el manejo de errores 401; un interceptor es ahora el único lugar donde se decide cómo se recupera la autenticación. Hay tres consideraciones importantes. process.env.REACT_APP_API_URL es la convención de Create React App; en un proyecto Vite se debería usar import.meta.env.VITE_API_URL en su lugar. Si varias solicitudes fallan al mismo tiempo, cada una desencadena su propio proceso de actualización, lo que puede invalidar los tokens de actualización rotativos; la solución se explica en por qué los errores 401 concurrentes hacen que los usuarios se desconecten. Además, almacenar los tokens en localStorage los expone a cualquier vulnerabilidad XSS, por lo que vale la pena considerar el uso de cookies httpOnly.
Errore 2: no devolver un valor desde un interceptor
Los interceptores forman una cadena, y cada eslabón debe pasar su valor al siguiente. Estos dos olvidan:
// ❌ Bad: Broken chain
api.interceptors.response.use((response) => {
console.log('Response received:', response.status);
// Forgot to return response!
});
api.interceptors.request.use((config) => {
config.headers['X-Request-ID'] = crypto.randomUUID();
// Forgot to return config!
});
El efecto a menudo se describe como que la solicitud se queda colgada, pero en las versiones actuales de Axios el fallo suele ser más directo y igual de confuso. Un interceptor de respuestas que no devuelve nada resuelve la promesa del llamante con undefined, por lo que const { data } = await api.get('/user') lanza un error de desestructuración lejos de su causa real. Un interceptor de solicitudes que no devuelve nada deja a Axios sin configuración, y la solicitud falla antes incluso de ser enviada. En ambos casos, el error aparece en otro lugar distinto al donde se originó.
Siempre devuelva lo que recibió y rechace explícitamente en los manejadores de errores para que el catch del llamante siga ejecutándose:
// ✅ Good: Complete chain
api.interceptors.request.use(
(config) => {
config.headers['X-Request-ID'] = crypto.randomUUID();
return config; // Critical
},
(error) => Promise.reject(error)
);
api.interceptors.response.use(
(response) => {
// Log without breaking the chain
console.log(`[${response.config.url}] ${response.status}`);
return response; // Critical
},
(error) => {
// Always reject so the component catch block fires
return Promise.reject(error);
}
);
La analogía con Express sigue vigente: olvidarse de next() detiene una solicitud de Express, y olvidarse de return interrumpe una cadena de interceptores.
Errore 3: adjuntar encabezados de autenticación manualmente
Copiar un encabezado Authorization en cada llamada trata un problema transversal como si fuera local. Tarde o temprano alguna llamada lo omite, lo formatea de manera diferente o envía Bearer null. El fragmento a continuación también muestra cuán fácil es cometer errores: en la llamada post el objeto del encabezado se pasa como cuerpo de la solicitud y la carga útil como configuración, ya que Axios espera post(url, data, config):
// ❌ Bad: Manual headers everywhere
api.get('/user', { headers: { Authorization: `Bearer ${token}` } });
api.post('/order', { headers: { Authorization: `Bearer ${token}` } }, payload);
api.get('/history', { headers: { Authorization: `Bearer ${token}` } });
Un interceptor de solicitudes elimina toda la categoría de errores al agregar el encabezado en un único lugar, pero solo cuando existe un token:
// ✅ Good: Global token injection
api.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Si utilizas TanStack Query, sus cachés también ofrecen ganchos globales para efectos secundarios. Un QueryCache onError puede redirigir en caso de 401 para cualquier consulta, y un MutationCache onError puede mostrar un mensaje emergente para cualquier mutación fallida:
// ✅ Good: QueryClient global handlers
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
if (error.response?.status === 401) {
window.location.href = '/login';
}
}
}),
mutationCache: new MutationCache({
onError: (error) => {
toast.error(error.message);
}
})
});
El principio subyacente es que la autenticación es una cuestión relacionada con el transporte, no con un componente, por lo que debe manejarse en la capa de transporte.
Carga de datos: cargadores de React Router y cachés de TanStack Query
Errore 4: realizar solicitudes en useEffect cuando debería hacerlo un cargador de ruta
El patrón habitual es montar, ejecutar un efecto, mostrar un indicador de carga, recibir los datos y renderizar:
// ❌ Bad: Component-level fetching
function Dashboard() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
api.get('/api/dashboard')
.then(res => setData(res.data))
.finally(() => setIsLoading(false));
}, []);
if (isLoading) return <Spinner />;
return <DashboardContent data={data} />;
}
Esto genera secuencias de solicitudes: el router renderiza el componente, solo entonces el efecto inicia la solicitud, y solo después de que esta regrese se actualiza el estado. Al navegar hacia adelante y hacia atrás, se vuelve a obtener los datos cada vez. Las versiones anteriores de React también advertían sobre el establecimiento de estado después del desmontaje; React 18 eliminó esa advertencia, pero la competencia entre una respuesta lenta y una más reciente sigue existiendo.
Las API de datos de React Router (disponibles con createBrowserRouter desde la v6.4) permiten que una ruta declare un loader que realiza la solicitud antes de que se renderice el elemento de la ruta, y un errorElement que se muestra si el loader lanza un error:
// ✅ Good: Route-level data loading
// router.jsx
import { createBrowserRouter } from 'react-router-dom';
import { Dashboard, dashboardLoader } from './Dashboard';
const router = createBrowserRouter([
{
path: '/dashboard',
element: <Dashboard />,
loader: dashboardLoader, // Runs before render
errorElement: <DashboardError />
}
]);
// Dashboard.jsx
import { useLoaderData } from 'react-router-dom';
export async function dashboardLoader() {
const response = await api.get('/api/dashboard');
if (response.status !== 200) {
throw new Response('Failed to load dashboard', { status: response.status });
}
return response.data;
}
export function Dashboard() {
const data = useLoaderData(); // Already available, no loading state needed
return <DashboardContent data={data} />;
}
Cuando se renderiza Dashboard, useLoaderData() ya cuenta con los datos, por lo que no hay estado de carga a nivel de componente ni código genérico relacionado con efectos. Hay dos detalles importantes que vale la pena conocer. La navegación en sí misma espera al cargador, por lo que aún se necesita un indicador global de pendiente controlado por useNavigation. Además, como Axios rechaza por defecto las respuestas que no sean de tipo 2xx, la comprobación response.status !== 200 en este cargador nunca detecta un estado de error; el rechazo se transmite automáticamente al errorElement.
Para datos secundarios que no deben bloquear la página, el cargador puede esperar a la solicitud crítica y devolver la lenta como una promesa sin resolver utilizando defer:
export async function dashboardLoader() {
const criticalData = await api.get('/api/summary');
const slowData = api.get('/api/analytics'); // Promise, not awaited
return defer({
summary: criticalData.data,
analytics: slowData.then(r => r.data)
});
}
La página se muestra con el resumen mientras se reciben los datos de análisis, generalmente mediante Await y Suspense. En React Router v7, defer ya no es necesario y los cargadores pueden devolver promesas directamente; por lo tanto, consulte la documentación correspondiente a la versión que está utilizando.
Error 5: thunks escritos a mano para cada solicitud
Redux Thunk supuso un gran avance cuando se introdujo, pero escribir uno por cada llamada a la API hoy en día implica mantener constantes de tipo de acción, creadores de acción, casos de reductor y banderas de estado para tareas que las herramientas modernas manejan de forma declarativa:
// ❌ Bad: Thunk boilerplate for simple fetching
const FETCH_USER_REQUEST = 'FETCH_USER_REQUEST';
const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
const fetchUser = (userId) => async (dispatch) => {
dispatch({ type: FETCH_USER_REQUEST });
try {
const res = await api.get(`/users/${userId}`);
dispatch({ type: FETCH_USER_SUCCESS, payload: res.data });
} catch (error) {
dispatch({ type: FETCH_USER_FAILURE, payload: error.message });
}
};
// Reducer boilerplate...
// Component mapStateToProps...
Esa estructura era razonable cuando no existía nada mejor. Para operaciones CRUD estándar, ahora resulta innecesaria.
RTK Query y TanStack Query ofrecen caché, deduplicación de solicitudes, recarga en segundo plano y estados de carga de forma predeterminada. Con RTK Query se describe una consulta base, incluyendo un gancho prepareHeaders que agrega el token, y se declaran los puntos de conexión; se generan automáticamente ganchos como useGetUserQuery y useUpdateUserMutation:
// ✅ Good: RTK Query handles the middleware
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const userApi = createApi({
reducerPath: 'userApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers) => {
const token = localStorage.getItem('token');
if (token) headers.set('authorization', `Bearer ${token}`);
return headers;
}
}),
endpoints: (builder) => ({
getUser: builder.query({
query: (id) => `/users/${id}`,
}),
updateUser: builder.mutation({
query: ({ id, ...body }) => ({
url: `/users/${id}`,
method: 'PUT',
body,
}),
}),
}),
});
export const { useGetUserQuery, useUpdateUserMutation } = userApi;
Detrás de esos mecanismos, RTK Query almacena en caché las respuestas por endpoint y argumento, cuenta cuántos componentes montados utilizan cada entrada y descarta aquellas que ya nadie usa. Tenga en cuenta que no normaliza las entidades de la misma manera que lo haría un adaptador de entidades creado manualmente; los resultados en caché se guardan tal como se devuelven. De cualquier forma, usted escribe la lógica de negocio y la biblioteca se encarga de los detalles técnicos.
Middleware de Redux y medidas de seguridad en la interfaz de usuario
Error 6: mutar el estado dentro del middleware de Redux
El middleware personalizado de Redux es un lugar adecuado para el registro de actividades, el análisis de datos y la transformación de acciones. No es el lugar adecuado para modificar el estado. El middleware se ejecuta entre el dispatch y el reducer, por lo que modificar el estado allí evita el seguimiento de cambios de Redux, dificulta la depuración con funcionalidades de viaje en el tiempo y confunde a DevTools:
// ❌ Bad: State mutation in middleware
const analyticsMiddleware = store => next => action => {
if (action.type === 'ADD_TO_CART') {
// Directly mutating state! Never do this.
store.getState().cart.lastAddedAt = Date.now();
}
return next(action);
};
La mutación también impide volver a renderizar nada, ya que Redux y sus enlaces detectan los cambios comparando referencias. Al modificar un campo del objeto existente, la referencia se mantiene idéntica, por lo que los suscriptores no observan ninguna actualización. La verificación de inmutabilidad en modo de desarrollo de Redux Toolkit marcará este tipo de mutación, lo cual es otra razón más para utilizarlo.
Restrinja los middleware a tareas de lectura, registro, transformación o reenvío de acciones, y mantenga todos los cambios de estado en los reducers. Aquí, el middleware rastrea la acción y agrega una marca de tiempo como meta, y el reducer realiza una actualización inmutable utilizando esa información:
// ✅ Good: Middleware only intercepts and forwards
const analyticsMiddleware = store => next => action => {
// Log the action before it reaches the reducer
analytics.track(action.type, {
payload: action.payload,
timestamp: Date.now(),
userId: store.getState().auth.userId,
});
// Optionally transform the action
if (action.type === 'ADD_TO_CART') {
action = {
...action,
meta: { addedAt: Date.now() }
};
}
return next(action); // Forward to reducer
};
// Reducer handles the state update
function cartReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_TO_CART':
return {
...state,
items: [...state.items, action.payload],
lastAddedAt: action.meta.addedAt, // Proper immutable update
};
default:
return state;
}
}
Tenga en cuenta la relación de dependencia: el reductor lee action.meta.addedAt, por lo que depende del middleware instalado. Generar la marca de tiempo en el creador de acciones (o en una función de callback prepare de Redux Toolkit) evita esa dependencia oculta. Una imagen mental útil: el middleware es como la oficina de correos que puede leer los sobres y poner sellos, pero nunca abre su estado bancario para cambiar el saldo.
Errore 7: tratar los límites de errores como algo secundario
Muchas aplicaciones tienen un único límite alrededor de la raíz, o ninguno. Los límites de errores funcionan como middleware para el árbol de componentes: capturan los errores de renderizado en sus hijos e impiden que un fallo deteriore toda la interfaz. Sin ellos, una respuesta de API mal formada, una propiedad faltante o un script de terceros con errores pueden dejar la página en blanco:
// ❌ Bad: No error boundaries
function App() {
return (
<div>
<Header />
<Sidebar />
<Dashboard /> {/* If this crashes, everything dies */}
<Footer />
</div>
);
}
Coloque los límites a nivel de diseño y de características para que una falla en el widget de análisis no elimine la navegación. Un límite es un componente de clase que establece el estado de error en getDerivedStateFromError, informa en componentDidCatch y muestra un contenido alternativo; al anidar varios, cada región cuenta con su propia capacidad de recuperación:
// ✅ Good: Strategic boundary placement
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <DefaultErrorUI error={this.state.error} />;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallback={<GlobalErrorPage />}>
<Header />
<div className="layout">
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<main>
<ErrorBoundary fallback={<WidgetError />}>
<AnalyticsWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError />}>
<RecentActivity />
</ErrorBoundary>
</main>
</div>
</ErrorBoundary>
);
}
Tenga en cuenta sus límites: los boundaries capturan los errores que se generan durante la renderización, en los métodos del ciclo de vida y en los constructores que se encuentran debajo de ellos, pero no los errores en los manejadores de eventos, en código asíncrono como temporizadores o promesas no esperadas, ni en el propio boundary. Estos últimos requieren manejadores comunes try/catch o manejadores globales. Si utiliza un framework con Componentes de Servidor, combine los boundaries del cliente con los archivos de errores a nivel de ruta (por ejemplo error.js en el Next.js App Router), capturando los errores del servidor por ruta y los errores del cliente por componente.
Desempeño y límites de la plataforma
Errore 8: operaciones intensivas dentro de los interceptores
Los interceptores son asíncronos en el sentido de las promesas, pero su código sigue ejecutándose en el hilo principal. La descifrado, transformación o análisis costosos que se realizan allí bloquean la renderización:
// ❌ Bad: Heavy work in interceptor
api.interceptors.response.use((response) => {
// This blocks rendering for large payloads
response.data = heavyDecryptAndTransform(response.data);
return response;
});
Una transformación de 200 milisegundos significa que la página ignora las entradas durante esos 200 milisegundos, lo que en un teléfono se siente como una aplicación congelada. Transfiera el procesamiento intensivo a un Web Worker o posponga cualquier tarea no esencial. Aquí, las respuestas de exportación se envían a un worker y el interceptor espera el resultado:
// ✅ Good: Async offloading for heavy work
api.interceptors.response.use(async (response) => {
if (response.config.url.includes('/export')) {
// Move heavy work off main thread
const worker = new Worker(new URL('./transform.worker.js', import.meta.url));
const transformed = await new Promise((resolve, reject) => {
worker.onmessage = (e) => resolve(e.data);
worker.onerror = reject;
worker.postMessage(response.data);
});
worker.terminate();
response.data = transformed;
}
return response;
});
Crear un nuevo worker por respuesta conlleva un costo de inicio, y postMessage copia los datos mediante clonación estructurada; por lo tanto, para llamadas frecuentes, reutilice un worker de larga duración y considere el uso de buffers transferibles. Para la encriptación o compresión, la API Web Crypto dentro de un worker, o bien trasladar el trabajo al servidor, mantiene la interfaz de usuario receptiva. Para obtener más información sobre este equilibrio, consulte cuándo trasladar el trabajo de la CPU a un Web Worker.
Error 9: tratar los middleware de Next.js como código de aplicación
El middleware de Next.js se ha ejecutado tradicionalmente en el entorno Edge, un aislamiento de V8 en lugar de Node.js completo. No puede acceder al contexto de React ni al estado del cliente, y allí no está disponible un pool de conexiones a bases de datos convencional. Aun así, es común encontrar validaciones complejas, consultas a bases de datos o lógica basada en estado en middleware.ts:
// ❌ Bad: Trying to run React logic in Edge Middleware
// middleware.ts
import { NextResponse } from 'next/server';
export async function middleware(request) {
// This won't work: no access to React context
const user = await db.user.findUnique(...); // No DB connection in Edge!
// This is risky: heavy computation in Edge
const result = complexAlgorithm(request.body);
return NextResponse.next();
}
Concéntrese en tareas ligeras a nivel de solicitud: reescrituras, verificaciones económicas de la presencia de cookies, agrupación A/B, filtrado de bots y enrutamiento basado en geografía. Este ejemplo redirige a los visitantes no autenticados lejos de /dashboard, envía un encabezado de experimento a la aplicación y utiliza matcher para ejecutarse únicamente en las rutas que lo necesitan:
// ✅ Good: Lightweight edge middleware
import { NextResponse } from 'next/server';
export function middleware(request) {
const token = request.cookies.get('token')?.value;
// Simple auth check: presence only
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// A/B testing: lightweight header injection
const experiment = request.cookies.get('exp')?.value || 'control';
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-experiment', experiment);
return NextResponse.next({
request: { headers: requestHeaders }
});
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*']
};
La validación real de sesiones debe realizarse en los controladores de rutas o en componentes del servidor con acceso total al servidor; el middleware funciona como portero, no como oficina interna. El soporte en tiempo de ejecución ha ido cambiando: las versiones recientes de Next.js han añadido una opción de entorno de ejecución Node.js para el middleware y han modificado la convención de nombres de archivos, por lo que es necesario verificar el comportamiento actual en la documentación de Next.js correspondiente a su versión.
Error 10: sobreingeniería de una aplicación pequeña
No todo proyecto necesita una serie de interceptores, middleware personalizado de Redux y procesadores. Es fácil encontrar sitios de portafolio de tres páginas con complejas cadenas de interceptores, restricciones alrededor de texto estático y una capa completa de consultas solo para un formulario de contacto:
// ❌ Bad: Over-engineered simple app
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
cacheTime: Infinity,
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
refetchOnWindowFocus: true,
refetchOnReconnect: true,
// ... 20 more options for a static site
}
}
});
Eso es culto al cargamento, no arquitectura: cada capa añade carga mental, mayor peso de los paquetes e indirección al depurar. Una aplicación con un par de rutas y una sola solicitud no necesita una estrategia de caché. La configuración también es internamente inconsistente: con staleTime: Infinity, los datos nunca se consideran obsoletos, por lo que las opciones de enfoque y recarga automática no surten efecto. Además, cacheTime fue renombrado a gcTime en TanStack Query v5.
Escala la infraestructura junto con la aplicación:
- Sitios estáticos o simples: llama directamente a
fetch, sin interceptores. - Aplicaciones pequeñas con autenticación: un interceptor de solicitud para los tokens y otro de respuesta para los errores 401.
- Aplicaciones medianas con enrutamiento: agrega cargadores de router y límites de errores a nivel de layout.
- Aplicaciones grandes con estado complejo: utilice RTK Query o TanStack Query junto con middleware personalizado.
Saber cuándo no añadir una abstracción forma parte del trabajo.
Pensamiento de middleware para aplicaciones React
El middleware no es solo una idea de backend. En React es la capa que mantiene a los componentes enfocados en la renderización mientras los problemas comunes se manejan una sola vez.
- Coloque la autenticación y la traducción de errores en la capa de transporte, en una única instancia del cliente, y siempre devuelva o rechace desde los interceptores.
- Cargue los datos de la ruta en cargadores o en un caché de consultas en lugar de usar los efectos de los componentes.
- Conservé los efectos secundarios únicamente en el middleware de Redux y deje que los reducers se encarguen de los cambios de estado.
- Utilice varios límites de errores y recuerde qué no pueden capturar.
- Aléje el trabajo intensivo del hilo principal y mantenga el middleware de borde ligero.
- Agregue cada capa solo cuando el tamaño de la aplicación lo justifique.
Leer más
- Redux Toolkit desde First Slice hasta Data Asincrónica: Un Modelo Mental Útil: Aprenda cómo se integran el store de Redux Toolkit, las slices, los reducers impulsados por Immer, los selectores y createAsyncThunk, utilizando un contador, un carrito de compras y una lista de productos.
- Por Qué WebSockets Se Atascan en Rutas API de Next.js y Cómo un Servidor Personal Lo Arregla: Entienda por qué un servidor ws dentro de una ruta pages/api nunca completa el handshake, y cómo manejar el evento de upgrade por su cuenta con un servidor personal de Next.js.