React Auth, загрузка дадзейнаў і адказы на адказы: Axios, React Router, Redux, TanStack Query
Дзесяць памылак у React, якія стосуюцца аутэнтыкацыі, завантажэння дадзэнняў і адзінакоў, і якія былі выправлены за дапамогою інтэрцэптараў Axios, ладераў React Router, мідлвэра Redux, TanStack Query і граніц адзінакоў.
Крыніцы: Stop Polluting Your Components: The React ‘Middleware’ Guide (Axios, Routers, and State)
У частыні сервера ніхто не пераглядае процес аутэнтыкацыі ў кожным працоўніку маршрута і не дадзеўвае логавання ў кожны кантролер; гэтае завадзе выкананае ў мідлвэры, якая запускаецца адно раз і прыменяецца да кожнага запиту. Кодавыя базы React часта паслягаюць гэтай дысцыпліне. Компаненты вынужданы самі кераваць вставкай токена, перенаправленнямі 401, логаванням адзінакоў і запошчаннем дадзэнняў, што прыводзіць да зайвага коду, нэўнацоўнага паведання і багоў, якія пазіраюць толькі тады, калі корыстнік натыкаецца на элементы па аднаковым порядку. У додатку React є свой жыцёвы цыкл запиту, у яком вызовы API, пераходы маршрутаў і распадзел стану праходзяць чераз шары, да якіх можна падключыцца. Шырокі падчынак раскрывае дзесяць распашчытных памылак у інтэрцэптарах Axios, ладчыках React Router, бібліятэках стану, межах адзінакоў і мідлвэры Next.js, і паказвае, дзе кожны аспект павінен знаходзіцца.
Аспекты транспорту: аутэнтыкацыя і інтэрцэптары Axios
Памылка 1: обработка адказоў 401 у кожным компаненте
Обработка аутантыкацыі ўсё чаща стае прычыной занадта сложных структураў компанентаў. Кожная функцыя запыту мае свой блок catch, які пераглядае наявнасць коду 401, чыстае запамятовальнік і перыяхоўвае:
// ❌ 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;
}
}
Парадоксальна гэта ситуацыя: па-першыя ўсё перарабатваецца, але ўсё равно ёсць слабасці. Пераход з локальнага зберагача на кукі значыць неабходнасць праверкі кожнага месца, дзе выкананы запыт. Дадаць механізм змены токена практычнаючы перенаправленне таксама значыць прабаву змяніць кожны файл занова. А простая занедбаная перапрацоўка заставляе дапыт застацца ў стане частковага выйшчага з абліковання.
Рашэннем являецца наявнасць інтэрцэптара адпаведзення Axios на спяльнай інстанцыі кліента, який бачыць кожнае адпаведзенне раней, чым яго бачыць будь-які компонент. У прыказанай нижэй версіі токен таксама дадаецца пад час выходу, а пад кодам 401 адбываецца адна спроба апавярожнення (захаваная праз флаг _retry, ўбліжаныя кілу), пасля чаго выкананы ўсеўначальны запыт з новым токенам; толькі якщо сама спроба апавярожнення не ўспіе, тады прыменяецца альтернатыўны спосаб — чыстка зберагача і перенаправленне.
// 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;
Компаненты тепер проста ўвозяць api і вызываюць яго, не ведаючы пра адрабатаванне стану 401; адны інтэрцэптар стаў ўжо ўсём месцам, дзе вырашаецца, як восстанавіць аутэнтыкацыю. Існуе тры застерэжэнні. process.env.REACT_APP_API_URL — это стандарт Create React App; у проекте Vite трэба чытаць import.meta.env.VITE_API_URL у замену. Якщо калькі запытов зазнаюць няудачы адразу, кожны з іх запускае сваее воснавленне, што можа зробіць недзейнарамі токены для воснавлення; рашэнне на гэтае праблемае прадстаўленае у чаму калькі 401 выключаюць корыстувачаў. А калі токены зберагаюцца ў localStorage, яны стаюць вяскраснымі для будзь-якіх багоў типу XSS, таму варта рассмотрзець викорыстанне httpOnly-кукі.
Памылка 2: не вяртаць значэнне з інтэрцэптара
Інтэрсэптары утвараюць ланцуг, і кожны з ўзлоў должен перадаць сваю значэнню наступнаму. Шырокія з іх забываюць:
// ❌ 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!
});
Этот эфект часта апіляюцься як «завішанне запиту», але ў ныях версіях Axios адказ зазвычай бывае ўсё болей прымітным, але такім жа незрозумелым. Інтэрцэптар адказа, які не вяртае нічога, рашуе праблему вызывача за дапамою undefined, таму const { data } = await api.get('/user') выклікае памылку дэструктурыравання, якая не стосуецца самай прычыны. Інтэрцэптар запита, які не вяртае нічога, заставляе Axios працаваць без налашчэнняў, і запит зазнае няўдаласці ўсё раней, чым ён будзе адправлены. У будзь-якім случае баг проявляецца не там, дзе ён быў створаны.
Заўжды вяртайце тое, што янытрымалі, і ясна адхіліце запит у хэндлерах памылак, каб catch вызывача все равно працаваў:
// ✅ 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);
}
);
Аналагія з Express застаўся: забывчына next() спрычынае затримку запиту Express, а забывчына return наражае на паўзу ланцуг перехоплювачаў.
Памылка 3: ручнае дадаванне заголовкаў аутэнтыкацыі
Копіюванне заголовка Authorization у кожны запыт розглядае проблему, якая стосуецца кількох частак, як локальную. Раней чы пазней якісь запыт працягне ўжо без яго, сформатуе яго інакш або вышле Bearer null. Наводзімы ў прыкладзе, наскількі лёгка можна памыліцца: у запыті post об’ект заголовка перадаецца як тэла запыту, а даны — як настройкі, таму што Axios чакае формат 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}` } });
Інтерцептар запыткаў усунеў весь клас адзінакоў па аднадзеўнам даданні загалоўка, толькі калі існуе токен:
// ✅ 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;
});
Якщо вы викорыстоўваеце TanStack Query, яго кэшы таксама прысвячаюць глобальныя хукі для рэалізацыі побачных эфектаў. QueryCache onError можа перенаправіць на стан 401 для будзь-якага запиту, а MutationCache onError можа паказаць паведамленне пра адмову для будзь-якай неудачнай мутацыі:
// ✅ 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);
}
})
});
Асновныя прынцыпы заключаюцца у тым, што аутентыкацыя ўзначае проблему транспортування, а не проблему компанентаў, таму яе трэба обрабляць на слое транспортування.
Завантажэнне дадзеных: кэшы React Router loaders і TanStack Query
Памылка 4: запрашэнне дадзеных у useEffect, калі гэта можа здзейсніцца праз лоадер маршрута
Звычны патэрн: запуск компанента, выкананне эфекту, показ візуальнага індыкатора загрузкі, прыем дадзеных, апрацоўка іх:
// ❌ 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} />;
}
Ён стварае «водоспады запыткаў»: раутэр атрымляе компанент, толькі пасля чаго эфект пачынае запыт, а стан апошнія адкроюецца толькі пасля таго, як запыт вернуўся. Переходы туда-назад зноў і зноў атрымляюць даны. У старэйшых версіях React таксама былі паведамленні пра неабходнасць не змянюваць стан пасля анмонтавання; у React 18 гэтыя паведамленні былі адмоўлены, але проблема супернічання між медленным і новым адпаведзеннем застаецца рэальной.
API-я дадзеных React Router (доступныя з createBrowserRouter з версіі v6.4) дазволяюць маршруту задаць loader, які атрымляе даны прычымоўна да атрымання элемента маршруту, а таксама errorElement, які атрымляецца, якщо loader выклікае адказку:
// ✅ 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} />;
}
Калі Dashboard атрыбутуецца, useLoaderData() уже мае даны, таму няма стану загрузкі на рэвэле компанента чыраўкі коду. Є два моменты, якія варта знать. Сама навігацыя чакае на загрузчык, таму вам яшчэ патрэбны глобальны показчык статусу, керованы useNavigation. А так как Axios за замовчаннем адхоўляе адпаведзі, якія не ў спектре 2xx, то перакананне response.status !== 200 у гэтым загрузчыку ніколі не бачыць статусу адказу; адхоўленне сама по сабе пераходзіць у errorElement.
Для дадатковых даных, якія не должны блакануць сторанку, загрузчык можа чакаць на критычны запит і перадаць медлены як нераспалоўваную прамісу за дапамою 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)
});
}
Старонка адражуецца з кантактом, паколькі аналітыка прыходзіць, зазвычай через Await і Suspense. У React Router v7 параметр defer больш не патрэбен, і ладеры можаць безпасебна вяртаць прамісы, таму пераканаўцеся з дакументацыяю да версіі, яку вы викорыстоўвайце.
Памылка 5: ручнае стварэнне thunks для кожнага запиту
Redux Thunk стаў вялікім крокам уперад пры своем з’яўленні, але напісанне такога элемента для кожнага вызову API сёння значыць неабходнасць падтрымкі констант типа дзеяння, функцый стварэння дзеяння, варыянтавання редуктара і флагаў статусу для задач, якія сучасныя інструменты можаць адмаўляцыйна обрабляць:
// ❌ 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...
Такая структура была разумной, калі не існавала нічога кращага. Для стандартных задач CRUD яна зараз не патрэбна.
RTK Query і TanStack Query з’яўляюцься з можлівасцю кэшавання, усунення дублікацый запытак, фонавага падтрымкі падзеяў падзялу дадзэнняў і статусаў завантажэння. За дапамогою RTK Query вы задаеце базовы запыт, укладаючы хук prepareHeaders, які дадае токен, і прызначаеце канцэнтры; для вас генеруюцца хуки на кшталт useGetUserQuery і 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;
За гэтымі хукамі RTK Query кэшуе адказы па кожнам канцэнтры і аргументу, пісае, скількі компанентаў выкарыстоўвае кожны з эўрадактав, і адбракоўвае тыя, якія больш нікім не выкарыстоўваюцца. Заўважыце, што ён не нормалізуе энтытэты так, як гэта робіць ручна створаны адаптар энтытэта; кэшаваныя рэзультаты зберагаюцца такімі, якія былі вярнуты. У будзь-якім случае вы пішаеце бізнес-логіку, а бібліятэка адпавядае за тэхнічныя аспекты.
Мідлвэры Redux і захоўніцы UI
Памылка 6: мутацыя стану ўнутрь мідлвэра Redux
Адзінакавы мідлвэр Redux — гарны спосаб для логавання, аналітыкі і трансфармацыі дзеянняў. Гэта не тое месца, дзе трэба змяніць стан. Мідлвэр работае між вызначэнням дзеяння і редукерам, таму змены стану там абходзяць систему адстэйкання змян у Redux, спаношваюць адлагоджэнне з викорыстаннем функцыі time-travel і плутаюць інструменты 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);
};
Гэта мутацыя таксама не можа перзапісаць нічога, таму што Redux і яго прыёмнікі выклікаюць змены па прыпорэчэнні рэферэнсаў. Змена поля ў існуючым об’екте заставляе рэферэнс застацца тым жа, таму падпісчыкі не бачаць жадных апдэйтаў. Перакананне ў незменнасці ў режыме разработкі з Redux Toolkit пакажае такі тип мутацыі, што яшчэ адна прычына яго викорыстоўвання.
Обмежыце мідлвэры чытаннем, логаваннем, трансфармаваннем або перадачай дзеянняў, а кожную змену стану залучайце ў редукеры. Хутчэй мідлвэр фіксуе дзеяння і дадае часовую метку як meta, а редукер выкаанае апдэйт, які не зменяе стан, выкарыстоўваючы яе:
// ✅ 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;
}
}
Зверніце увагу на зв’язок: редюсер чытае action.meta.addedAt, таму ён залежыць ад установленага мідлвэра. Стварэнне пазнакі часу ў функцыі стварэння дзеяння (або у калебку prepare у Redux Toolkit) запобегае такой скрытай залежнасці. Прыемны атрыбут: мідлвэр — це пакоўнай, якая можа чытаць конверты і ставіць маркі, але ніколі не відкрывае вашу банкавую выкладку, каб змяніць залишак.
Памылка 7: спрыяванне граніцам каштоўкаў як да дадатковага рашэння
У багацых дапытках є адна граніца навакола корневага элемента, або ўзагалі яе няма. Граніцы каштоўкаў функцыонуюць як мідлвэр для дрэва компанентаў: яны ловяць бягавочныя каштоўкі ў своіх дзецях і не дазволяюць аднаму бягавочнаму каштоўку знішчыць весь інтерфейс. Без іх некоректны адпаведзень API, відсутняя власцівасць або неякісны скрытак трэцьей стороны можу зробіць весь аблака пустым:
// ❌ Bad: No error boundaries
function App() {
return (
<div>
<Header />
<Sidebar />
<Dashboard /> {/* If this crashes, everything dies */}
<Footer />
</div>
);
}
Разместіце межы на рэв’ю і на рэв’ю элементаў, каб збой у віджэце аналітыкі не пазбавіў можлівасці навігацыі. Межа — это класовы компонент, які задае статус адказу ў getDerivedStateFromError, фіксуе проблему ў componentDidCatch і адрасавае альтернатыўны варыянт; ўзаемнае розмешчэнне кальколька такіх меж дае кожнаму регіону сваёе вярнэнне да нормальнага стану:
// ✅ 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>
);
}
Згадайце працэ ўскорэння: граніцы ловяяць бягі, якія выдаюцца пад час атрыбутавання, у методах жыцёвага цыклу і ў канстрактарах, якія знаходзяцца нижэй, але не бягі ў обробніках запуска, у асінхронным кодзе, такім как таймеры або обявы, якія не чакаюць атрыбутавання, або ў самай граніцы. Для такіх случаў патрэбны звычайныя try/catch або глобальныя обробнікі. Якщо вы викорыстоўваете фрэймворк з серверскімі компанентамі, спалучыце граніцы кліента з яго файламі для адліквідацыі бягоў на рэвеі (напрыклад, error.js у Next.js App Router), ловячы бягі сервера па рэвеях і бягі кліента па компанентах.
Прыдатнасць і граніцы платформы
Памылка 8: тэжкія вычысленні ўнутрь інтэрцэптараў
Інтэрцэптары ў значэнні обяв ёсць асінхронныя, але іх код заўсёды выконваецца на галоўнай ніткі. Дорогія процесы дэкрыптавання, трансформацыі або парсавання там блакуе атрыбутавання:
// ❌ Bad: Heavy work in interceptor
api.interceptors.response.use((response) => {
// This blocks rendering for large payloads
response.data = heavyDecryptAndTransform(response.data);
return response;
});
200 мілісекунд пры трансфармацыі — цэх 200 мілісекунд, калі сторанка ігнаруе вхідныя даны, што на тэлефоне стварае адчуванне заморожанага дапрыязда. Перакладзіце важкія обробкі ў Web Worker або адкладзіце все, што не ўскладнелае роботу. У гэтым случае адпаведныя рэспансы адправляюцца да рабочага процеса, а інтэрцэптар чакае на рэзультат:
// ✅ 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;
});
Стварэнне новага працоўніка за кожны адпаведзенне мае пачатковыя витраты, а postMessage копіюе даныя за дапамою структураванага клонавання, таму для частых вызоў краща воспаўляць працоўнік, які працуе даволі дзяўна, і рассмотріць можлівасць викорыстання буфероў, якія можна перадаваць. Для шифравання аб компрэсіі викорыстанне Web Crypto API у працоўніку аб перанесенне вычысленняў на сервер дапамагае падтрымваць адпаведнасць інтерфейсу. Чырвонейшыя моменты гэтага компромісу можна пазнакоміцца у статыі пра тое, калі трэба пераносіць вычысленняў CPU у Web Worker.
Памылка 9: адносаванне мідлвэра Next.js да коду аплікацыі
Мідлвэрк Next.js традыцыйна працюе на сэрвісе Edge runtime, які ўоснованы на V8, а не на цэлым Node.js. Ён не можа абмовіцца пра контэкст React чыста стан кліента, і там няма можлівасці выкорыстоўваць звычайны пулы з’яўленняў да базы дадзеных. Усё-такі часта можна пабачыць складную пераверкі, запыты да базы дадзеных чыста логіку, якая залежыць ад стану, у файле 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();
}
Закладывайцеся на лёгкія задачы на рэгламентны ўровень: перапісвы, простыя перагляды наявнасці кукі, розпад на групы A/B, фільтрацыю ботаў і маршрутызацыю на аднойчыну з геаграфіяй. У гэтым прыкладзе неаутентыфікованыя візітары перенаправляюцца з /dashboard, заголовак эксперыменту перадаецца дапрацоўкі, а matcher выкорыстоўваецца толькі для обработкі тых пацек, якія гэта патрабуюць:
// ✅ 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*']
};
Рэальная перакананне сэсіі павінна выконвалівацца ў хэндлерах маршрутаў або ў компанентах сервера, якія маюць полны доступ да сервера; мідлвэр — гэта праўільнік у воратах, а не аддзін з элементаў внутрашньага кантролю. Падтрымка пад час выконання змінілася: няўзабавыя выданні Next.js дадалі опцыю выконання на Node.js для мідлвэра і зменілі правілы назвавання файлаў, таму неабходна пацікавіцца чынным станом у документацыі Next.js для вашай версіі.
Памылка 10: занадта складная рэалізацыя маленькага прыемніка
Не кожны проект патрабуе набора інтерцэптараў, спецыяльных мідлвэраў на базе Redux і рабочых процэсаў. Лёгка знайсці веб-сайты-портфолія з трыма староніцамі, якія выкарыстоўваюць складныя ланцюгі інтерцэптараў, меры захоплення статычнага тэксту і цэлы шар запытаў толькі для аднаго формы кантактаў:
// ❌ 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
}
}
});
Это ўжо культ карава, а не архітектура: кожны слой дадае дапаможную навантажэння на процесар, збільшывае вагу пакета і спрыяе непрямым способам падчас дэбаггінгу. Аплікацыя з калькама маршрутаў і аднам запитам не патрабуе стратэгіі кэшавання. Канфігурацыя таксама ўнутршна несупарадная: з параметрам staleTime: Infinity дадзеныя ніколі не вважаюцца застарэлымі, таму флагі focus і reconnect refetch не выкананыя. А параметр cacheTime у версіі TanStack Query v5 быў перайменаваны на gcTime.
Расшыроўваюце апаратную частку разам з аплікацыяй:
- Статычныя або простыя сайты: выкорыстоўваюце
fetchбезпосередна, без інтерцептараў. - Маленькія аплікацыі з аутэнтыкацыяй: адзін інтерцептар запитаў для токэнаў і адзін інтерцептар адпаведзей для кода 401.
- Середнія аплікацыі з маршрутацыяй: дадаюце ладчыкі маршрутаў і межы для адліквідацыі бядаў на рэверсі лейауту.
Знанне таго, калі не трэба дадаць абстракцыю, ўжо є часткай работы.
Падход да мідлвэра для аплікацый на React
Мідлвэр — гэта не толькі ідея для бэкэнда. У React ён ўтварае шар, які дапамагае компонентам сфокусавацца на відраслі, а спакушаныя задачы адмахваюцца адразу.
- Аутентыкацыю і пераклад бягаў размешчаюць у шары транспорту, на аднай інстанцыі кліента, і завжды вяртаюць або адхоўляюць рэзультаты з інтэрцэптараў.
- Даныя маршрута завантажаюць у лоадеры або кэш запытанняў узамен на відрыхтанні компонентам.
- У мідлвэре Redux застаюцься толькі пабочныя эфекты, а змены стану керуюцься редукерамі.
- Выкарыстоўваюць калькі бягаў для адхоўлення бягаў, і памятаюць, што яны не можаць падхопіць.
- Важкія вычысленні выводзяць за межы галоўнай ніті, а мідлвэр на краях залишаць лёгкім.
- Дадзіце кожны слой толькі тады, калі габарыты прыкладнення гэта дазваляе.