Организация кода
Архитектурный паттерн нужен не ради названия. Он фиксирует границы ответственности, направление зависимостей и правила изменения приложения. Хороший выбор уменьшает связанность и делает feature проще для разработки, тестирования и удаления. Начинать стоит с простого feature-first устройства и усложнять его, только когда это решает наблюдаемую проблему команды.
Границы и структура feature
Что такое feature-first структура и почему она обычно лучше папок по типу файлов?
Короткий ответ
Feature-first группирует код по пользовательской возможности: orders, profile, checkout. Внутри feature живут его UI, маршруты, state, API-клиент и тесты. Папки components, services, models на весь проект кажутся простыми в начале, но со временем смешивают несвязанные домены и делают ownership неясным.
Полный ответ
Feature-first группирует код по пользовательской возможности: orders, profile, checkout. Внутри feature живут его
UI, маршруты, state, API-клиент и тесты. Папки components, services, models на весь проект кажутся простыми в
начале, но со временем смешивают несвязанные домены и делают ownership неясным.
Общие элементы выносят в shared только после появления нескольких реальных потребителей. Domain-код одной feature не
должен импортировать внутренности другой feature: для этого нужен небольшой публичный API или явный orchestration слой.
src/
features/
orders/
api/
model/
ui/
orders.routes.ts
profile/
shared/
ui/
lib/Как разделить presentational и container components?
Короткий ответ
Presentational component получает данные и callbacks через API, отображает UI и не знает, откуда пришли данные. Container связывает route, use case, состояние и presentational UI. Это не жесткое правило: маленький component может содержать оба слоя. Разделение окупается, когда UI переиспользуется, загрузка усложняется или component становится трудно тестировать.
Полный ответ
Presentational component получает данные и callbacks через API, отображает UI и не знает, откуда пришли данные. Container связывает route, use case, состояние и presentational UI. Это не жесткое правило: маленький component может содержать оба слоя. Разделение окупается, когда UI переиспользуется, загрузка усложняется или component становится трудно тестировать.
В React container обычно использует hook и передает props. В Angular container инжектирует facade или service, а
presentational component получает input() и отдает событие через output().
function OrdersPage() {
const {orders, cancelOrder} = useOrders();
return (
<OrdersList
orders={orders}
onCancel={cancelOrder}
/>
);
}@Component({
selector: 'app-orders-list',
template: `
@for (order of orders(); track order.id) {
<button
type="button"
(click)="cancel.emit(order.id)"
>
{{ order.number }}
</button>
}
`,
})
export class OrdersListComponent {
readonly orders = input.required<ReadonlyArray<Order>>();
readonly cancel = output<string>();
}@Component({
imports: [OrdersListComponent],
template: `
<app-orders-list
[orders]="orders()"
(cancel)="ordersFacade.cancel($event)"
/>
`,
})
export class OrdersPageComponent {
protected readonly ordersFacade = inject(OrdersFacade);
protected readonly orders = this.ordersFacade.orders;
}Что означает правило dependency direction?
Короткий ответ
Зависимости должны идти от внешних деталей к более стабильному коду: UI зависит от application use case, use case зависит от domain-контракта, а HTTP-клиент реализует этот контракт. Тогда изменение React component, Angular template или REST endpoint не заставляет менять business rule.
Полный ответ
Зависимости должны идти от внешних деталей к более стабильному коду: UI зависит от application use case, use case зависит от domain-контракта, а HTTP-клиент реализует этот контракт. Тогда изменение React component, Angular template или REST endpoint не заставляет менять business rule.
Практически это означает: не передавать HttpClient, React Query result или ActivatedRoute в domain-функции; DTO не
делать domain-моделью; не импортировать component из data-access слоя. Не каждой feature нужны все слои, но направление
зависимостей важно всегда.
Что такое facade pattern во frontend и чем он отличается от state manager?
Короткий ответ
Facade дает UI маленький API предметной области: данные для отображения и операции вроде load(), approve() или retry(). Она скрывает cache, store, HTTP и детали преобразования DTO. State manager хранит и изменяет состояние; facade может использовать state manager, но не обязана.
Полный ответ
Facade дает UI маленький API предметной области: данные для отображения и операции вроде load(), approve() или
retry(). Она скрывает cache, store, HTTP и детали преобразования DTO. State manager хранит и изменяет состояние;
facade может использовать state manager, но не обязана.
В React эту роль часто играет custom hook. В Angular это обычно service с readonly signals или Observable API. UI не
должен вызывать HttpClient или dispatch произвольных actions напрямую.
@Injectable({providedIn: 'root'})
export class OrdersFacade {
private readonly api = inject(OrdersApi);
private readonly state = signal<OrdersState>({orders: [], status: 'idle'});
readonly orders = computed(() => this.state().orders);
readonly isLoading = computed(() => this.state().status === 'loading');
async load(): Promise<void> {
this.state.update((state) => ({...state, status: 'loading'}));
const orders = await this.api.getAll();
this.state.set({orders, status: 'ready'});
}
}Как отделить DTO от domain model?
Короткий ответ
DTO повторяет контракт транспорта и может содержать строки дат, nullable-поля или неудачные названия backend. Domain model описывает данные, удобные и безопасные для приложения. Адаптер в data-access слое валидирует и преобразует DTO; остальной код не зависит от формы ответа API.
Полный ответ
DTO повторяет контракт транспорта и может содержать строки дат, nullable-поля или неудачные названия backend. Domain model описывает данные, удобные и безопасные для приложения. Адаптер в data-access слое валидирует и преобразует DTO; остальной код не зависит от формы ответа API.
type OrderDto = {
created_at: string;
id: string;
total_cents: number;
};
type Order = {
createdAt: Date;
id: string;
total: number;
};
const toOrder = (dto: OrderDto): Order => ({
createdAt: new Date(dto.created_at),
id: dto.id,
total: dto.total_cents / 100,
});И React, и Angular используют такой adapter одинаково: отличие только в том, кто вызывает repository или API service.
Паттерны UI-композиции
Когда выбирать composition вместо множества boolean props?
Короткий ответ
Если API component разрастается до isCompact, showHeader, hasActions, isEditable и комбинации флагов создают разные layouts, лучше передавать части интерфейса как children, slots или отдельные components. Composition делает допустимые комбинации видимыми в месте использования и уменьшает число неявных режимов.
Полный ответ
Если API component разрастается до isCompact, showHeader, hasActions, isEditable и комбинации флагов создают
разные layouts, лучше передавать части интерфейса как children, slots или отдельные components. Composition делает
допустимые комбинации видимыми в месте использования и уменьшает число неявных режимов.
React использует children и compound components. Angular использует content projection (ng-content) и directives для
маркировки проецируемых частей.
<Dialog>
<Dialog.Title>Удалить заказ?</Dialog.Title>
<Dialog.Actions>
<button type="button">Удалить</button>
</Dialog.Actions>
</Dialog><app-dialog>
<h2 dialog-title>Удалить заказ?</h2>
<div dialog-actions><button type="button">Удалить</button></div>
</app-dialog>Что такое custom hook в React и какой его аналог в Angular?
Короткий ответ
Custom hook повторно использует stateful логику React: например, debounce, загрузку или взаимодействие с браузерным API. Он подчиняется rules of hooks и вызывается только из React component или другого hook.
Полный ответ
Custom hook повторно использует stateful логику React: например, debounce, загрузку или взаимодействие с браузерным API. Он подчиняется rules of hooks и вызывается только из React component или другого hook.
В Angular логику обычно выносят в service, функцию, использующую inject() в injection context, directive или pipe.
Выбор определяется lifetime: service может быть root-, route- или component-scoped, тогда как hook живет вместе с
вызвавшим его component.
// Angular: component-scoped state
@Injectable()
export class SearchState {
readonly query = signal('');
readonly normalizedQuery = computed(() => this.query().trim().toLowerCase());
}
@Component({
providers: [SearchState],
template: `
<input
[value]="state.query()"
(input)="setQuery($event)"
/>
`,
})
export class SearchComponent {
protected readonly state = inject(SearchState);
protected setQuery(event: Event): void {
const target = event.target;
if (!(target instanceof HTMLInputElement)) {
return;
}
this.state.query.set(target.value);
}
}В production для сложной формы лучше использовать form control; пример показывает именно scoped lifetime состояния.
Когда нужен compound component pattern?
Короткий ответ
Compound components делят один составной виджет на согласованные части: Tabs.List, Tabs.Panel, Select.Option. Родитель владеет общим состоянием и accessibility-правилами, а вложенные части получают контекст. Паттерн полезен для переиспользуемых UI primitives, но избыточен для одноразовой разметки feature.
Полный ответ
Compound components делят один составной виджет на согласованные части: Tabs.List, Tabs.Panel, Select.Option.
Родитель владеет общим состоянием и accessibility-правилами, а вложенные части получают контекст. Паттерн полезен для
переиспользуемых UI primitives, но избыточен для одноразовой разметки feature.
В React связь часто строят через Context. В Angular для этого подходят parent component, contentChildren(), DI по
element injector и directives. Особенно важно централизованно поддержать клавиатурную навигацию, focus и ARIA, а не
оставить это каждому consumer.
Слои и масштабирование
Что такое layered architecture и когда она достаточна?
Короткий ответ
В простой layered architecture UI обращается к application/service слою, тот использует data-access, а domain содержит типы и чистые правила. Она достаточно хороша для большинства frontend-приложений, если границы feature понятны и слои не обходятся импортами.
Полный ответ
В простой layered architecture UI обращается к application/service слою, тот использует data-access, а domain содержит типы и чистые правила. Она достаточно хороша для большинства frontend-приложений, если границы feature понятны и слои не обходятся импортами.
orders/
ui/ # components и templates
application/ # facade, use cases, orchestration
domain/ # types, policies, pure functions
data-access/ # HTTP, cache, DTO adaptersНе нужно создавать пустые папки и интерфейсы для каждого маленького component. Слой появляется, когда в нем появляется самостоятельная ответственность.
Что такое clean architecture во frontend и какой ее trade-off?
Короткий ответ
Clean architecture отделяет domain и use cases от фреймворка, UI и инфраструктуры. Например, use case placeOrder зависит от OrdersRepository, а Angular service или React API client реализует его через HTTP. Это упрощает тесты и замену интеграций, когда правила действительно сложны и долгоживущи.
Полный ответ
Clean architecture отделяет domain и use cases от фреймворка, UI и инфраструктуры. Например, use case placeOrder
зависит от OrdersRepository, а Angular service или React API client реализует его через HTTP. Это упрощает тесты и
замену интеграций, когда правила действительно сложны и долгоживущи.
Trade-off — дополнительные interfaces, adapters и переходы между моделями. Для CRUD-экрана с небольшой логикой это может скрыть простое действие за несколькими файлами. Правильный подход: оставить pure business rules независимыми, но вводить полный набор портов и adapters только при реальной выгоде.
Как организовать server state и client state в React и Angular?
Короткий ответ
Server state принадлежит backend и требует cache, invalidation, retry и работы со stale-данными. В React для него часто используют TanStack Query; локальный UI state оставляют в useState, reducer или небольшом store. В Angular server state можно держать в service с resource или RxJS/query layer, а локальный UI state — в signal() рядом с component.
Полный ответ
Server state принадлежит backend и требует cache, invalidation, retry и работы со stale-данными. В React для него часто
используют TanStack Query; локальный UI state оставляют в useState, reducer или небольшом store. В Angular server
state можно держать в service с resource или RxJS/query layer, а локальный UI state — в signal() рядом с component.
Не смешивайте эти роли в одном глобальном store без причины. Фильтр, который должен переживать ссылку, принадлежит URL; черновик формы — form layer; данные заказа — cache/repository; открытая панель — локальному UI.
Когда оправданы micro frontends?
Короткий ответ
Micro frontends оправданы при независимых командах, релизных циклах и доменах, когда организационная граница важнее стоимости интеграции. Они не являются способом автоматически разделить большой monolith: добавляют versioning, наблюдаемость, design system, routing, shared auth, performance budget и контракт между host и remote.
Полный ответ
Micro frontends оправданы при независимых командах, релизных циклах и доменах, когда организационная граница важнее стоимости интеграции. Они не являются способом автоматически разделить большой monolith: добавляют versioning, наблюдаемость, design system, routing, shared auth, performance budget и контракт между host и remote.
Angular и React можно объединить через host, Web Components, Module Federation или другой runtime/build-time механизм. Но данные лучше передавать через узкий versioned contract, а не через общий mutable global store. Если команды выпускают продукт вместе, обычно дешевле остаться в modular monolith с feature boundaries.
Основные варианты интеграции из исходного материала:
| Вариант | Пример | Главный trade-off |
|---|---|---|
| Build time | versioned NPM package @company/header | простой runtime, но общий release cycle |
| Runtime | Module Federation remote | независимый deploy, но versioning и runtime failures |
| Server side | ESI/server composition | мало client orchestration, но сложнее infrastructure |
| Iframe | isolated legacy widget | сильная изоляция, но сложные UX, routing и communication |
<esi:include src="https://header.example.com/header" />
<iframe
src="https://legacy.example.com/widget"
title="Legacy widget"
></iframe>Выбор паттерна
Как аргументировать архитектурный выбор на интервью?
Короткий ответ
Начните с контекста: размер команды, число feature, сложность domain-правил, независимость релизов, требования к SSR, кешу и тестированию. Затем назовите простое решение и критерии роста.
Полный ответ
Начните с контекста: размер команды, число feature, сложность domain-правил, независимость релизов, требования к SSR, кешу и тестированию. Затем назовите простое решение и критерии роста.
Например: «Для нового Angular feature я начну с route-level feature folder, presentational components и facade service с signals. DTO останутся в data-access. Если появятся сложные async streams, добавлю RxJS; если нескольким командам понадобятся единые transitions, selectors и DevTools, рассмотрю NgRx. Для React аналогично: feature folder, custom hook как facade и query layer для server state, без глобального store до появления shared client state».
Такой ответ показывает, что паттерн выбран под ограничения, а не скопирован из другого проекта.
Полный каталог из исходного gist
Ниже собраны все оставшиеся паттерны и примеры из исходного материала. Примеры на React дополнены реализацией или практическим эквивалентом для Angular.
Component-Based Architecture
Что такое Atomic Design?
Короткий ответ
Atomic Design делит design system на atoms (Button, Input), molecules (SearchField), organisms (Header), templates и pages. Паттерн удобен для общего языка дизайнеров и разработчиков, но категории не должны влиять на domain boundaries: ProductCard может быть organism в design system и одновременно частью feature catalog.
Полный ответ
Atomic Design делит design system на atoms (Button, Input), molecules (SearchField), organisms (Header),
templates и pages. Паттерн удобен для общего языка дизайнеров и разработчиков, но категории не должны влиять на domain
boundaries: ProductCard может быть organism в design system и одновременно частью feature catalog.
shared/ui/
button/ # atom
search-field/ # molecule
header/ # organism
features/catalog/
product-page/ # page с реальными даннымиВ React и Angular иерархия одинакова. В Angular atoms обычно являются components, directives или pipes; templates и pages часто соответствуют routed components.
State Management Patterns
Как устроены Flux и Redux?
Короткий ответ
Flux задает однонаправленный цикл: пользовательское событие создает action, dispatcher передает его store, store вычисляет новое состояние, view отображает результат. Redux сводит идею к одному store, readonly state и чистым reducers.
Полный ответ
Flux задает однонаправленный цикл: пользовательское событие создает action, dispatcher передает его store, store вычисляет новое состояние, view отображает результат. Redux сводит идею к одному store, readonly state и чистым reducers.
type CounterAction = {readonly type: 'increment'} | {readonly type: 'decrement'};
function counterReducer(state: number, action: CounterAction): number {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
}
}React обычно подключает Redux Toolkit через useSelector() и useDispatch(). В Angular тот же поток дает NgRx:
component выбирает данные selector-ом и отправляет event через store.dispatch(). Для небольшого feature достаточно
service с readonly signal и явными methods.
@Injectable({providedIn: 'root'})
export class CounterStore {
private readonly countState = signal(0);
readonly count = this.countState.asReadonly();
increment(): void {
this.countState.update((count) => count + 1);
}
}Чем React Context + Hooks отличается от Angular DI + signals?
Короткий ответ
В React provider передает значение через component tree, а custom hook инкапсулирует чтение context и проверку наличия provider. В Angular эту задачу решают DI hierarchy и service. Provider на component или route дает scoped instance, а providedIn: ‘root’ — application-wide instance.
Полный ответ
В React provider передает значение через component tree, а custom hook инкапсулирует чтение context и проверку наличия
provider. В Angular эту задачу решают DI hierarchy и service. Provider на component или route дает scoped instance, а
providedIn: 'root' — application-wide instance.
const ThemeContext = createContext<ThemeContextValue | null>(null);
function ThemeProvider({children}: PropsWithChildren) {
const [theme, setTheme] = useState<Theme>('light');
const toggle = () => setTheme((value) => (value === 'light' ? 'dark' : 'light'));
return <ThemeContext value={{theme, toggle}}>{children}</ThemeContext>;
}@Injectable()
export class ThemeState {
private readonly themeState = signal<'light' | 'dark'>('light');
readonly theme = this.themeState.asReadonly();
toggle(): void {
this.themeState.update((theme) => (theme === 'light' ? 'dark' : 'light'));
}
}
export const appRoutes: Routes = [
{
path: '',
providers: [ThemeState],
loadComponent: async () => (await import('./shell')).Shell,
},
];Context удобен для редко меняющихся cross-cutting values. Часто меняющийся server state лучше отдавать query/cache layer, чтобы не провоцировать широкие rerenders.
Как реализовать state machine без набора противоречивых flags?
Короткий ответ
Состояние описывают discriminated union, а переходы — явной функцией. XState полезен для nested и parallel states, guards, actors и визуализации; для небольшого flow достаточно TypeScript reducer.
Полный ответ
Состояние описывают discriminated union, а переходы — явной функцией. XState полезен для nested и parallel states, guards, actors и визуализации; для небольшого flow достаточно TypeScript reducer.
type FetchState<T> =
| {readonly status: 'idle'}
| {readonly status: 'loading'}
| {readonly status: 'success'; readonly data: T}
| {readonly status: 'error'; readonly message: string};
type FetchEvent<T> =
| {readonly type: 'fetch'}
| {readonly type: 'resolve'; readonly data: T}
| {readonly type: 'reject'; readonly message: string};Такая модель одинаково используется в React reducer и Angular signal store и не позволяет одновременно получить
isLoading: true и hasError: true.
Design Patterns
Как Observer pattern проявляется в React и Angular?
Короткий ответ
Observer подписывает потребителей на изменения source. DOM events, RxJS Observable, store subscriptions и signals — разные реализации этой идеи. Вручную писать mutable массив observers обычно не нужно: важны teardown и lifetime.
Полный ответ
Observer подписывает потребителей на изменения source. DOM events, RxJS Observable, store subscriptions и signals — разные реализации этой идеи. Вручную писать mutable массив observers обычно не нужно: важны teardown и lifetime.
const searchResults$ = queryControl.valueChanges.pipe(
debounceTime(250),
distinctUntilChanged(),
switchMap((query) => searchApi.search(query)),
);В Angular template может читать поток через async pipe или signal через toSignal(). В React внешние stores безопасно
подключаются через useSyncExternalStore().
Когда нужен Factory pattern?
Короткий ответ
Factory централизует выбор реализации по type или configuration. В React это может быть mapping типа поля на component; в Angular — mapping на dynamic component type или DI factory provider. Большой switch стоит заменить registry, если варианты добавляют независимые модули.
Полный ответ
Factory централизует выбор реализации по type или configuration. В React это может быть mapping типа поля на component;
в Angular — mapping на dynamic component type или DI factory provider. Большой switch стоит заменить registry, если
варианты добавляют независимые модули.
type FieldKind = 'email' | 'password' | 'select';
const fieldByKind = {
email: EmailField,
password: PasswordField,
select: SelectField,
} satisfies Record<FieldKind, Type<unknown>>;
function getFieldComponent(kind: FieldKind): Type<unknown> {
return fieldByKind[kind];
}Почему Singleton лучше получать через dependency injection?
Короткий ответ
Ручной getInstance() создает скрытый global state и усложняет тесты. В React singleton module допустим для stateless API client, но зависимости проще передавать через provider. В Angular lifecycle singleton-а контролирует injector.
Полный ответ
Ручной getInstance() создает скрытый global state и усложняет тесты. В React singleton module допустим для stateless
API client, но зависимости проще передавать через provider. В Angular lifecycle singleton-а контролирует injector.
@Injectable({providedIn: 'root'})
export class ApiClient {
private readonly http = inject(HttpClient);
getUsers(): Observable<ReadonlyArray<User>> {
return this.http.get<ReadonlyArray<User>>('/api/users');
}
}providedIn: 'root' создает один instance в root environment injector. Route или component provider намеренно создает
отдельный scoped instance, поэтому «singleton» всегда относится к конкретному injector.
Что дает Module pattern в современном TypeScript?
Короткий ответ
Исторически IIFE скрывал mutable variables в closure и возвращал public API. ES modules дают encapsulation напрямую: неэкспортированные значения остаются private для файла.
Полный ответ
Исторически IIFE скрывал mutable variables в closure и возвращал public API. ES modules дают encapsulation напрямую: неэкспортированные значения остаются private для файла.
let result = 0;
const history: string[] = [];
export function add(value: number): number {
result += value;
history.push(`add ${value}`);
return result;
}
export function getHistory(): ReadonlyArray<string> {
return [...history];
}Для user-specific или test-specific state module singleton опасен. В Angular такое состояние лучше держать в service с подходящим provider scope, в React — в component/provider state.
Architectural Styles
Чем MVC и MVVM полезны для понимания современного frontend?
Короткий ответ
MVC отделяет Model, View и Controller. MVVM добавляет ViewModel — подготовленное для binding состояние и команды view. Современные React и Angular приложения редко реализуют эти паттерны буквально, но идея границ остается полезной.
Полный ответ
MVC отделяет Model, View и Controller. MVVM добавляет ViewModel — подготовленное для binding состояние и команды view. Современные React и Angular приложения редко реализуют эти паттерны буквально, но идея границ остается полезной.
type UserViewModel = {
readonly displayName: string;
readonly canEdit: boolean;
};
const toUserViewModel = (user: User, permissions: Permissions): UserViewModel => ({
displayName: user.name.toUpperCase(),
canEdit: permissions.includes('user:edit'),
});В React hook часто играет роль Controller/ViewModel. В Angular facade с computed() готовит view state, а template
остается View. Two-way binding не делает Angular «чистым MVVM»: архитектуру определяют границы, а не синтаксис binding.
Что такое Jamstack?
Короткий ответ
Jamstack отделяет заранее сгенерированный markup от динамических API и раздает static assets через CDN. Подходит для контентных сайтов, документации и marketing pages; authenticated dashboard обычно требует CSR или hybrid rendering.
Полный ответ
Jamstack отделяет заранее сгенерированный markup от динамических API и раздает static assets через CDN. Подходит для контентных сайтов, документации и marketing pages; authenticated dashboard обычно требует CSR или hybrid rendering.
content + templates = static HTML at build time
static HTML = CDN
interactive actions = APIs or serverless functionsПримеры: Astro с content API, Next.js с CMS. Angular поддерживает аналогичный вариант через prerendering и route-based render modes.
Что такое Islands Architecture?
Короткий ответ
Страница в основном состоит из static HTML, а независимые интерактивные islands гидратируются отдельно. В Astro это выражают directives client:load, client:idle и client:visible.
Полный ответ
Страница в основном состоит из static HTML, а независимые интерактивные islands гидратируются отдельно. В Astro это
выражают directives client:load, client:idle и client:visible.
<Article content={post} />
<SearchBar client:load />
<Newsletter client:visible />
<Comments client:idle />Angular не является islands framework, но incremental hydration и @defer решают похожую задачу при SSR: тяжелый блок
можно загрузить и гидратировать по viewport или interaction trigger.
@defer (on viewport) {
<app-comments />
} @placeholder {
<app-comments-skeleton />
}Как работает Module Federation?
Короткий ответ
Host загружает exposed modules независимо развернутого remote в build time или runtime. Shared dependencies требуют совместимых версий; singleton: true снижает риск двух runtime framework, но не заменяет контракт версий.
Полный ответ
Host загружает exposed modules независимо развернутого remote в build time или runtime. Shared dependencies требуют
совместимых версий; singleton: true снижает риск двух runtime framework, но не заменяет контракт версий.
new ModuleFederationPlugin({
name: 'catalog',
filename: 'remoteEntry.js',
exposes: {'./ProductCard': './src/product-card'},
shared: {react: {singleton: true}, 'react-dom': {singleton: true}},
});Для Angular remote экспортирует routes или standalone component, а host подключает их через federation tooling. Shared state remote-модулем создает сильную runtime coupling; предпочтительнее versioned events или API contracts.
Application Models
Чем SPA отличается от MPA?
Короткий ответ
SPA загружает application shell и переключает routes на клиенте; последующая навигация быстрая, но initial bundle, JavaScript availability и client state становятся критичными. MPA получает новый HTML document для каждого перехода; она проще, надежнее без JavaScript и естественно индексируется, но теряет in-memory state между страницами.
Полный ответ
SPA загружает application shell и переключает routes на клиенте; последующая навигация быстрая, но initial bundle, JavaScript availability и client state становятся критичными. MPA получает новый HTML document для каждого перехода; она проще, надежнее без JavaScript и естественно индексируется, но теряет in-memory state между страницами.
React Router и Angular Router строят SPA. Next.js, Astro и Angular SSR могут сочетать server navigation, hydration и client navigation, поэтому реальный продукт часто является hybrid, а не чистой SPA или MPA.
Из чего состоит PWA?
Короткий ответ
PWA использует Web App Manifest для installability и service worker для offline cache, update и background behavior. Cache strategy выбирают по типу ресурса; бездумный cache-first для API может показывать устаревшие данные.
Полный ответ
PWA использует Web App Manifest для installability и service worker для offline cache, update и background behavior. Cache strategy выбирают по типу ресурса; бездумный cache-first для API может показывать устаревшие данные.
self.addEventListener('fetch', (event) => {
const request = event.request;
event.respondWith(caches.match(request).then((cached) => cached ?? fetch(request)));
});В Angular базовый service worker добавляет ng add @angular/pwa; правила assets и data groups задаются в
ngsw-config.json. React не содержит PWA layer, его настраивает build framework или Workbox.
Какие бывают hybrid applications?
Короткий ответ
Capacitor помещает web app в native shell и дает bridge к Camera, Filesystem и другим platform APIs. Ionic добавляет mobile-oriented UI components. React Native использует JavaScript/TypeScript, но рендерит native views, а не DOM.
Полный ответ
Capacitor помещает web app в native shell и дает bridge к Camera, Filesystem и другим platform APIs. Ionic добавляет mobile-oriented UI components. React Native использует JavaScript/TypeScript, но рендерит native views, а не DOM.
const photo = await Camera.getPhoto({
allowEditing: true,
quality: 90,
resultType: CameraResultType.Uri,
});Angular приложения часто используют Ionic + Capacitor. Контракт platform service лучше абстрагировать через DI, чтобы browser tests могли подставить fake implementation.
Rendering Strategies
Как выбирать между CSR, SSR и SSG?
Короткий ответ
CSR рендерит после загрузки JavaScript и подходит authenticated applications с высокой интерактивностью. SSR создает HTML на каждый initial request и подходит динамическим индексируемым страницам. SSG генерирует HTML на build time и лучше всего работает для стабильного контента.
Полный ответ
CSR рендерит после загрузки JavaScript и подходит authenticated applications с высокой интерактивностью. SSR создает HTML на каждый initial request и подходит динамическим индексируемым страницам. SSG генерирует HTML на build time и лучше всего работает для стабильного контента.
export const serverRoutes: ServerRoute[] = [
{path: 'dashboard', renderMode: RenderMode.Client},
{path: 'products/:id', renderMode: RenderMode.Server},
{path: 'about', renderMode: RenderMode.Prerender},
];React выбирает эти режимы через framework вроде Next.js. Angular SSR поддерживает route-level CSR, SSR и prerendering; после SSR hydration делает HTML интерактивным.
Что такое ISR и какой аналог есть вне Next.js?
Короткий ответ
ISR сохраняет производительность static page, но периодически или по событию регенерирует ее. Во время regeneration пользователь может получить предыдущую версию, поэтому система допускает окно stale content.
Полный ответ
ISR сохраняет производительность static page, но периодически или по событию регенерирует ее. Во время regeneration пользователь может получить предыдущую версию, поэтому система допускает окно stale content.
export const revalidate = 60;
export default async function ProductPage() {
const product = await getProduct();
return <Product product={product} />;
}Это не универсальная browser capability, а функция hosting/framework cache. В Angular похожую семантику строят через prerender + deployment rebuild/webhook, CDN stale-while-revalidate или server cache вокруг SSR.
Чем React Server Components отличаются от Angular SSR?
Короткий ответ
RSC остаются server-only и не добавляют свой code в client bundle; client components нужны для state, events и browser APIs. Angular SSR обычно выполняет те же components на server для initial HTML, а затем гидратирует их на client.
Полный ответ
RSC остаются server-only и не добавляют свой code в client bundle; client components нужны для state, events и browser APIs. Angular SSR обычно выполняет те же components на server для initial HTML, а затем гидратирует их на client.
async function BlogPost({id}: {readonly id: string}) {
const post = await db.posts.findById(id);
return (
<article>
<h1>{post.title}</h1>
<LikeButton postId={id} />
</article>
);
}Для Angular ближайшие инструменты — server routes, SSR/prerender, incremental hydration и @defer, но это не эквивалент
RSC protocol один к одному.
Что такое Edge Rendering?
Короткий ответ
Edge function выполняет SSR или personalization в CDN location рядом с пользователем. Это уменьшает network latency, но runtime часто ограничивает Node.js APIs, execution time и package size.
Полный ответ
Edge function выполняет SSR или personalization в CDN location рядом с пользователем. Это уменьшает network latency, но runtime часто ограничивает Node.js APIs, execution time и package size.
export default async function handler(request: Request): Promise<Response> {
const city = new URL(request.url).searchParams.get('city') ?? 'Moscow';
const weather = await fetch(`https://api.example.com/weather?city=${encodeURIComponent(city)}`);
return Response.json(await weather.json());
}React и Angular приложение может вызывать edge BFF одинаково. Возможность рендерить сам framework на edge зависит от adapter и hosting provider, поэтому этот runtime нужно проверять отдельно.
Service Patterns
Когда нужен Backend for Frontend?
Короткий ответ
BFF агрегирует backend services и возвращает DTO, оптимизированный под конкретный UI. Например, web dashboard одним запросом получает user, последние orders и recommendations, а mobile BFF возвращает более компактную модель.
Полный ответ
BFF агрегирует backend services и возвращает DTO, оптимизированный под конкретный UI. Например, web dashboard одним запросом получает user, последние orders и recommendations, а mobile BFF возвращает более компактную модель.
app.get('/api/dashboard', async (request, response) => {
const userId = request.auth.userId;
const [user, orders, suggestions] = await Promise.all([
userService.getUser(userId),
orderService.getOrders(userId),
recommendationService.getForUser(userId),
]);
response.json({user, recentOrders: orders.slice(0, 5), suggestions});
});BFF уменьшает waterfalls и скрывает topology microservices, но дублирование web/mobile BFF нужно контролировать. Для React и Angular его контракт одинаков; frontend data-access adapter преобразует BFF DTO в domain model.
Чем GraphQL Gateway отличается от API Gateway?
Короткий ответ
GraphQL Gateway дает typed schema и позволяет client выбрать поля. Resolvers агрегируют data sources, но требуют защиты от дорогих queries, DataLoader против N+1 и отдельной cache strategy. API Gateway маршрутизирует REST/gRPC/ WebSocket, централизует auth, rate limits, observability и иногда response aggregation.
Полный ответ
GraphQL Gateway дает typed schema и позволяет client выбрать поля. Resolvers агрегируют data sources, но требуют защиты от дорогих queries, DataLoader против N+1 и отдельной cache strategy. API Gateway маршрутизирует REST/gRPC/ WebSocket, централизует auth, rate limits, observability и иногда response aggregation.
query GetUser($id: ID!) {
user(id: $id) {
name
posts {
title
}
}
}app.use('/api/users', createProxyMiddleware({target: 'http://user-service:3001'}));
app.use('/api/products', createProxyMiddleware({target: 'http://product-service:3002'}));GraphQL client может использовать Apollo, urql или generated SDK. Angular получает SDK через DI service, React — через provider/hook; schema types лучше генерировать, а не дублировать вручную.
Performance Patterns
Как реализовать code splitting?
Короткий ответ
Route-based splitting выносит feature в отдельный chunk. Component-based splitting откладывает тяжелый editor, chart или dialog до момента использования.
Полный ответ
Route-based splitting выносит feature в отдельный chunk. Component-based splitting откладывает тяжелый editor, chart или dialog до момента использования.
const Dashboard = lazy(() => import('./dashboard'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
);
}export const routes: Routes = [
{
path: 'dashboard',
loadComponent: async () => (await import('./dashboard/dashboard')).Dashboard,
},
];В Angular для части template используют @defer; для routes — loadComponent или loadChildren. Chunk boundary стоит
ставить по feature и реальной стоимости, иначе множество мелких requests ухудшит загрузку.
Чем lazy loading ресурсов отличается от code splitting?
Короткий ответ
Code splitting создает chunks, lazy loading определяет момент их загрузки. Изображения лениво загружаются нативным loading=“lazy”, scripts используют defer или async, а произвольный resource можно связать с Intersection Observer.
Полный ответ
Code splitting создает chunks, lazy loading определяет момент их загрузки. Изображения лениво загружаются нативным
loading="lazy", scripts используют defer или async, а произвольный resource можно связать с Intersection Observer.
<img
src="product.webp"
loading="lazy"
width="640"
height="480"
alt="Товар"
/>
<script
src="analytics.js"
defer
></script>@defer (on viewport; prefetch on idle) {
<app-recommendations />
} @placeholder {
<app-recommendations-skeleton />
}Размеры изображения задают явно, чтобы lazy loading не создавал layout shift.
Когда memoization улучшает производительность?
Короткий ответ
Memoization полезна для измеримо дорогого pure calculation или стабильного selector. В React используют useMemo, useCallback и memo; React Compiler может оптимизировать часть случаев автоматически. В Angular computed() лениво кеширует значение до изменения зависимых signals, а pure pipe кеширует последний набор arguments.
Полный ответ
Memoization полезна для измеримо дорогого pure calculation или стабильного selector. В React используют useMemo,
useCallback и memo; React Compiler может оптимизировать часть случаев автоматически. В Angular computed() лениво
кеширует значение до изменения зависимых signals, а pure pipe кеширует последний набор arguments.
const filtered = useMemo(
() => products.filter((product) => categories.includes(product.category)),
[products, categories],
);readonly filteredProducts = computed(() => {
const categories = this.selectedCategories();
return this.products().filter((product) => categories.includes(product.category));
});Memoization имеет стоимость сравнения, cache и усложнения dependencies. Применять ее ко всем значениям заранее не нужно.
Как работает virtualization больших списков?
Короткий ответ
Virtualization оставляет в DOM только видимое окно элементов и небольшой overscan. React использует react-window или React Virtuoso; Angular — CDK virtual scrolling. Fixed item height проще и дешевле variable-height measurement.
Полный ответ
Virtualization оставляет в DOM только видимое окно элементов и небольшой overscan. React использует react-window или React Virtuoso; Angular — CDK virtual scrolling. Fixed item height проще и дешевле variable-height measurement.
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({index, style}) => <div style={style}>{items[index].name}</div>}
</FixedSizeList><cdk-virtual-scroll-viewport
itemSize="50"
class="viewport"
>
<div *cdkVirtualFor="let item of items; trackBy: trackById">{{ item.name }}</div>
</cdk-virtual-scroll-viewport>Паттерн нужен для таблиц, feeds, chat history и file trees с тысячами строк. Для десятков элементов стоимость библиотеки обычно выше пользы.
Какие бывают prefetch и preload?
Короткий ответ
dns-prefetch заранее резолвит host, preconnect устанавливает DNS/TCP/TLS, prefetch получает вероятный будущий resource с низким priority, а preload сообщает о critical resource текущей страницы.
Полный ответ
dns-prefetch заранее резолвит host, preconnect устанавливает DNS/TCP/TLS, prefetch получает вероятный будущий
resource с низким priority, а preload сообщает о critical resource текущей страницы.
<link
rel="preconnect"
href="https://api.example.com"
/>
<link
rel="prefetch"
href="/chunks/dashboard.js"
/>
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
/>React Router/framework может prefetch route на hover или viewport. Angular Router поддерживает preloading strategies для lazy routes; data можно prefetch через resolver или query cache. Aggressive prefetch конкурирует с critical resources и трафиком пользователя.
Что такое progressive и incremental hydration?
Короткий ответ
Server сначала присылает готовый HTML, затем framework постепенно подключает event handlers и client state. Critical UI гидратируется раньше, below-the-fold — при viewport/idle/interaction. React использует streaming и Suspense boundaries; Astro — islands; Angular — incremental hydration с hydrate triggers у @defer.
Полный ответ
Server сначала присылает готовый HTML, затем framework постепенно подключает event handlers и client state. Critical UI
гидратируется раньше, below-the-fold — при viewport/idle/interaction. React использует streaming и Suspense boundaries;
Astro — islands; Angular — incremental hydration с hydrate triggers у @defer.
@defer (hydrate on viewport) {
<app-comments />
} @placeholder {
<app-comments-skeleton />
}Граница должна быть независимой и иметь стабильный server/client output. Иначе hydration mismatch или раннее событие пользователя приведет к неожиданному UI.
Code Organization Strategies
Когда выбирать monorepo?
Короткий ответ
Monorepo хранит несколько applications и libraries в одном repository. Он полезен для atomic changes, общего design system, shared types и единых CI rules. Цена — tooling, dependency graph, access control и необходимость affected cache.
Полный ответ
Monorepo хранит несколько applications и libraries в одном repository. Он полезен для atomic changes, общего design system, shared types и единых CI rules. Цена — tooling, dependency graph, access control и необходимость affected cache.
apps/
web/
admin/
docs/
packages/
ui/
api-client/
types/
tooling/Nx хорошо работает и с Angular, и с React; pnpm workspaces/Turborepo — framework-neutral варианты. Library boundary должна отражать ownership и API, а не быть папкой ради папки.
Чем Vertical Slice отличается от feature-based structure?
Короткий ответ
Feature-based structure группирует связанный код по domain. Vertical Slice идет дальше: use case содержит все нужные presentation, application и data-access части и минимально зависит от соседних slices.
Полный ответ
Feature-based structure группирует связанный код по domain. Vertical Slice идет дальше: use case содержит все нужные presentation, application и data-access части и минимально зависит от соседних slices.
features/
checkout/
place-order/
place-order-page.ts
place-order.facade.ts
place-order.api.ts
place-order.spec.ts
order-history/
shared/В React .tsx components и hooks живут внутри slice. В Angular routed component, facade/service, resolver и data-access
могут находиться рядом. Barrel экспортирует только public API feature; deep imports из другой feature запрещают lint
rules.
Когда layer-based architecture полезна, а когда мешает?
Короткий ответ
Папки presentation, business, data, infrastructure хорошо показывают техническое направление зависимостей и подходят небольшому приложению. В большом продукте одна feature оказывается разбросана по всему repository, а изменения затрагивают несколько команд.
Полный ответ
Папки presentation, business, data, infrastructure хорошо показывают техническое направление зависимостей и
подходят небольшому приложению. В большом продукте одна feature оказывается разбросана по всему repository, а изменения
затрагивают несколько команд.
Компромисс — feature-first сверху и layers внутри крупной feature. Inner domain не импортирует UI или HttpClient; data-access реализует ports; presentation зависит от application API.
Почему shared state через Module Federation опасен?
Короткий ответ
Remote store технически можно expose и подключить в нескольких applications, но это связывает release versions, framework runtime и state schema. Ошибка загрузки remote блокирует всех consumers.
Полный ответ
Remote store технически можно expose и подключить в нескольких applications, но это связывает release versions, framework runtime и state schema. Ошибка загрузки remote блокирует всех consumers.
new ModuleFederationPlugin({
name: 'sharedState',
exposes: {'./store': './src/store'},
shared: ['react', 'redux'],
});Для cross-microfrontend communication предпочтительнее URL, backend state, versioned browser events или небольшой host contract. Общий store допустим только при едином ownership и синхронном release policy.
Testing Patterns
Что такое Testing Trophy?
Короткий ответ
Testing Trophy делает foundation из static checks, затем использует unit tests для pure logic, больше integration tests для взаимодействия частей и немного E2E для критических flows. Проценты не являются правилом: распределение зависит от риска.
Полный ответ
Testing Trophy делает foundation из static checks, затем использует unit tests для pure logic, больше integration tests для взаимодействия частей и немного E2E для критических flows. Проценты не являются правилом: распределение зависит от риска.
E2E: few critical flows
Integration: most behavior
Unit: pure isolated logic
Static: TypeScript and lintReact Testing Library и Angular Testing Library проверяют DOM behavior. Angular TestBed нужен для DI/templates, а
Playwright — для настоящего routing, browser APIs и integration с backend.
Как применять Arrange-Act-Assert в Angular и React?
Короткий ответ
Arrange готовит данные и dependencies, Act выполняет одно значимое действие, Assert проверяет observable result. В zoneless Angular после действия нужно дождаться scheduled rendering.
Полный ответ
Arrange готовит данные и dependencies, Act выполняет одно значимое действие, Assert проверяет observable result. В zoneless Angular после действия нужно дождаться scheduled rendering.
it('shows the updated title', async () => {
const fixture = TestBed.createComponent(ProfileCard);
fixture.componentInstance.title.set('Architect');
await fixture.whenStable();
expect(fixture.nativeElement.querySelector('h2')?.textContent).toContain('Architect');
});В React Testing Library Act обычно выполняет userEvent, а Assert ищет accessible role/text. Комментарии AAA не нужны,
если структура короткого теста очевидна.
Чем stub, mock, spy и fake отличаются друг от друга?
Короткий ответ
В Angular fake подставляют через {provide: UsersRepository, useClass: FakeUsersRepository} или InjectionToken. В React его передают provider-у/hook dependency. Проверять state/result предпочтительнее, чем каждый внутренний вызов.
Полный ответ
- Stub возвращает заранее заданное значение.
- Mock записывает interaction и проверяет protocol вызовов.
- Spy оборачивает реальную функцию и наблюдает за ней.
- Fake является упрощенной, но рабочей реализацией, например in-memory repository.
class FakeUsersRepository implements UsersRepository {
private readonly users = new Map<string, User>();
save(user: User): Promise<void> {
this.users.set(user.id, user);
return Promise.resolve();
}
findById(id: string): Promise<User | undefined> {
return Promise.resolve(this.users.get(id));
}
}В Angular fake подставляют через {provide: UsersRepository, useClass: FakeUsersRepository} или InjectionToken. В React
его передают provider-у/hook dependency. Проверять state/result предпочтительнее, чем каждый внутренний вызов.
Что такое Page Object Model?
Короткий ответ
Page Object скрывает selectors и повторяемые действия E2E page. Он должен описывать user intent, а не превращаться в копию всей DOM structure.
Полный ответ
Page Object скрывает selectors и повторяемые действия E2E page. Он должен описывать user intent, а не превращаться в копию всей DOM structure.
class LoginPage {
constructor(private readonly page: Page) {}
async open(): Promise<void> {
await this.page.goto('/login');
}
async login(username: string, password: string): Promise<void> {
await this.page.getByLabel('Username').fill(username);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', {name: 'Sign in'}).click();
}
}Один Page Object одинаково тестирует React и Angular UI, потому что работает с доступным browser contract, а не с framework internals.
Best Practices and Guidelines
Как применять SOLID во frontend без over-engineering?
Короткий ответ
В React зависимость можно передать через props/context, в Angular — через DI. Не следует создавать interface для каждой class: abstraction нужна на настоящей границе или при нескольких implementations.
Полный ответ
- SRP: component отображает profile, facade загружает user, formatter форматирует данные.
- OCP: новый variant добавляется через mapping или composition без переписывания consumers.
- LSP: API и cache repositories соблюдают одинаковый contract.
- ISP: card получает
Pick<User, 'id' | 'name'>, а не огромный object со всеми полями. - DIP: use case зависит от repository token/interface, infrastructure предоставляет implementation.
export const USERS_REPOSITORY = new InjectionToken<UsersRepository>('UsersRepository');
export const usersRepositoryProvider = {
provide: USERS_REPOSITORY,
useClass: HttpUsersRepository,
} satisfies Provider;В React зависимость можно передать через props/context, в Angular — через DI. Не следует создавать interface для каждой class: abstraction нужна на настоящей границе или при нескольких implementations.
Как Separation of Concerns выглядит на практике?
Короткий ответ
Data-access получает DTO, domain pure function сортирует и фильтрует products, application facade координирует loading, а component отображает готовый view model.
Полный ответ
Data-access получает DTO, domain pure function сортирует и фильтрует products, application facade координирует loading, а component отображает готовый view model.
const selectAvailableProducts = (products: ReadonlyArray<Product>): ReadonlyArray<Product> =>
products.filter((product) => product.stock > 0).toSorted((left, right) => right.price - left.price);
@Injectable()
export class ProductsFacade {
private readonly productsState = signal<ReadonlyArray<Product>>([]);
readonly availableProducts = computed(() => selectAvailableProducts(this.productsState()));
}React custom hook может быть application boundary, Angular service/facade — аналогом. Pure transformation остается framework-neutral и тестируется без DOM/TestBed.
Где проходит граница DRY?
Короткий ответ
DRY устраняет повторение одного знания, а не похожий syntax. Универсальный useFetch(url) или DataService быстро теряет cancellation, validation, cache keys и domain semantics. Абстракция полезна, когда повторился стабильный protocol.
Полный ответ
DRY устраняет повторение одного знания, а не похожий syntax. Универсальный useFetch(url) или DataService<T> быстро
теряет cancellation, validation, cache keys и domain semantics. Абстракция полезна, когда повторился стабильный
protocol.
const loadUser = () => http.get<UserDto>('/api/user').pipe(map(toUser));
const loadProducts = () => http.get<ReadonlyArray<ProductDto>>('/api/products').pipe(map(toProducts));Эти функции похожи технически, но имеют разные contracts и invalidation rules. Query client или shared HTTP adapter может убрать инфраструктурное повторение, не стирая domain names.
Как реализовать Progressive Enhancement?
Короткий ответ
Базовый HTML flow работает через browser navigation/form submit, JavaScript добавляет inline validation, optimistic UI и submission без reload. Server остается источником проверки данных.
Полный ответ
Базовый HTML flow работает через browser navigation/form submit, JavaScript добавляет inline validation, optimistic UI и submission без reload. Server остается источником проверки данных.
<form
action="/api/contact"
method="post"
>
<label>
Email
<input
name="email"
type="email"
required
/>
</label>
<textarea
name="message"
required
></textarea>
<button type="submit">Отправить</button>
</form>React framework action или Angular submit handler может перехватить форму после hydration. Если продукт является чистой authenticated SPA, полноценный no-JS fallback может быть нецелесообразен, но semantic HTML и native behavior остаются полезными.
Что входит в performance budget?
Короткий ответ
Budget задает проверяемые пределы для initial JavaScript/CSS, изображений, LCP, CLS, INP и server latency. Он должен проверяться в CI и на real-user monitoring, потому что Lighthouse lab run не отражает всех устройств и сетей.
Полный ответ
Budget задает проверяемые пределы для initial JavaScript/CSS, изображений, LCP, CLS, INP и server latency. Он должен проверяться в CI и на real-user monitoring, потому что Lighthouse lab run не отражает всех устройств и сетей.
{
"budgets": [
{
"type": "initial",
"maximumWarning": "350kb",
"maximumError": "500kb"
}
]
}Angular CLI поддерживает budgets в angular.json; React setup зависит от bundler/framework. Lighthouse CI может
проверять LCP, CLS и total blocking time, а bundle analyzer помогает найти причину превышения.
Что означает Accessibility First для component architecture?
Короткий ответ
Semantic element, accessible name, keyboard interaction, focus management и visible focus проектируются как часть API component. Одного role=“dialog” недостаточно: modal должен управлять initial focus, trap, Escape, background inert и возвратом focus.
Полный ответ
Semantic element, accessible name, keyboard interaction, focus management и visible focus проектируются как часть API
component. Одного role="dialog" недостаточно: modal должен управлять initial focus, trap, Escape, background inert и
возвратом focus.
<dialog
#dialog
aria-labelledby="dialog-title"
>
<h2 id="dialog-title">Удалить заказ?</h2>
<button
type="button"
(click)="dialog.close()"
>
Отмена
</button>
</dialog>React может использовать native elements или headless accessible library. В Angular для сложных widgets доступны CDK и
Angular Aria directives, которые реализуют focus и keyboard protocol; перед использованием @angular/aria нужно
добавить dependency и самостоятельно оформить visual states.
Choosing the Right Pattern
Как выглядит decision matrix из gist с поправкой на реальные ограничения?
Короткий ответ
Количество страниц само по себе не требует micro frontends. Главные сигналы — organizational ownership, coupling, release cadence, performance profile и domain complexity.
Полный ответ
| Контекст | Стартовый выбор |
|---|---|
| Маленький продукт, 1–3 разработчика | feature folders, local state, CSR или SSG |
| Средний продукт, 4–10 разработчиков | public feature APIs, lazy routes, facade/query layer |
| Большой продукт, несколько команд | monorepo, enforced boundaries, vertical slices; micro frontends только при независимых releases |
| Статический индексируемый контент | SSG/prerender + CDN |
| Динамический индексируемый контент | SSR + cache |
| Authenticated dashboard | CSR/hybrid, code splitting, virtualization для больших grids |
| Смешанный сайт | route-based rendering, islands или incremental hydration |
Количество страниц само по себе не требует micro frontends. Главные сигналы — organizational ownership, coupling, release cadence, performance profile и domain complexity.
Какие комбинации паттернов подходят типовым продуктам?
Короткий ответ
В Angular набор может выглядеть как standalone lazy routes + signals facade + HttpClient/RxJS + SSR/prerender. В React — framework routes + hooks/query cache + server/client component boundaries. Паттерны выбирают независимо от бренда framework.
Полный ответ
- E-commerce: component architecture, SSG/SSR для catalog, CSR для checkout, BFF, query cache и feature structure.
- SaaS dashboard: container/presentational, CSR, lazy routes, query layer, virtualization и vertical slices.
- Content platform: SSG, islands/incremental hydration, minimal client state и CDN.
- Enterprise application: monorepo, strict feature boundaries, facade/store; micro frontends только для автономных команд.
- Marketing site: Atomic Design/design system, SSG, image optimization, prefetch budget и progressive enhancement.
В Angular набор может выглядеть как standalone lazy routes + signals facade + HttpClient/RxJS + SSR/prerender. В React — framework routes + hooks/query cache + server/client component boundaries. Паттерны выбирают независимо от бренда framework.