대규모리액트 웹앱 개발
개요
대규모 앱이란 대규모팀에서 개발했거나 계속 진화하는 시스템을 말한다.
유지보수에 상당한 노력이 요구되는 앱이다.
이런 대규모앱 개발은 올바른 추상화 선택이 매우 중요하다.
프레임웍 자체의 추상화를 이해하는건 물론이고 그 기반이 되는 원리를 이해해야 더 유지보수 하기 쉬운걸 만들어낼 수 있다.
프로엠워크는 강력한 기본값을 제공하므로서 사용자가 불필요한 시행창오를 파하도록 해야한다.
좋은 프로그램은 여러 요건을 충족해야 하지만… 특히 대규모 프로그램은 확장성과 유지보수성이 중요하다.
이 책은 대규모 앱을 구축하거나 유지보수할때 사용하기 좋은 접근법에 대해 폭넓게 다루며 특히 리액트라는 프레임워크에 적용한 부분에 대해 설명한다. 하지만 이 책에 나오는 개념은 리액트 뿐만이 아니라 다양한 기반에 앱에 적용할 수 있다.
소프트웨어에 복잡성 관리하기
본질적인 복잡성과 불필요한 복잡성을 구분할줄 알아야한다.
복잡한 요구사항에 딘순한 코드를 요구하는건 사실 비상식적이다.
복잡성 설명하기
복집성은 도메인 자체가 갖은 복잡성인 내재적 복잡성과 문제를 해결하기 위해 한 과정에서 나오는 해결책이 가진 복잡성으로 나눌 수 있다.
해결책치 가진 복잡성은 원래 문제의 복잡성을 초과해서는 안된다.
커네빈 프레임워크
커네빈프레임워크 측정 은 프러그램의ㅜ복잡도를 5가지로 그분하려ㅜ설명한다 ibm에서 만들었는데 복집함을 이해하는데 도움이ㅜ된다. 이건 일정의 분류방법이고 의사결정에 대하누사고 방법이다. 커네빈 프레임워크 측정은 프로그램의 복잡도를 5가지로 구분하여 설명한다. IBM에서 만들었다고 하며, 복잡성을 이해하는데 도움이 된다. 이건 분류방법이자 하나의 사고 방식이다.
- 명확한(Clear): 원인-결과 명확 → 모범 사례 적용
- 복잡한(Complicated): 전문가 분석 필요 → 전문가 의견 활용 (명확하지만 복잡함)
- 복합적인(Complex): 예측 불가능 → 실험하고 학습 (명확하지 않음)
- 혼돈(Chaotic): 즉각 행동 필요 → 일단 안정화 (명확하지 않지만 일단 조치해야함)
- 무질서(Disorder): 영역 불분명 → 상황 파악 후 분류 (그냥 내가 모르는 분야임)
소프트웨어 설계 철학
의존성과 모호성이 복잡함을 만드는 주된 원인이다.
이런 모호성과 의존성은 변경에 대한 인지부하와 메타인지 비용이 증가하게 된다.
복잡성을 해소 하려면 이해하기 쉽고 개선하기 쉬운 방식으로 만들어야 한다.
모듈화는 좋은 전략이 될 수 있지만 또다른 복잡성을 만들어내므로 트레이드오프를 잘 따져 결정해야 한다.
가장 보편적인 전략은 상태와 로직을 분리하고, 추상화하여 복잡한 부분을 숨기는 전략이다. 함수형 프로그래밍을 이용하면 사이드이팩트를 예측하기 더 쉬워진다.
타르웅덩이 밖으로 -> 벅잡성있는 코드를 분리 (순수한 부분과 사이드 이팩트를 일으키는 부분을 명확히 분리하는게 좋다.)
단순함이 쉬움을 만든다.
문제를 먼저 단순화 시킨 후 그걸 바탕으로 추상화를 만들어 해결하라. 단순함이란… 적게 만드는걸 말하는게 아니라… 때로는 더 많이 만들어야 함을 의미한다.(단순함은 쉬움이 아니다)
편의성을 선택하기 보다는 복잡함이 덜하게 되는 언어를 선택하라.
문제를 해결하기 위한 도구는 간단해야 한다. 복잡하다면 그건 우발적복잡함(도메인의 어쩔수없는 복잡함에 의한 것이 아닌)이다.
단순함과 쉬움 모두 좋지만… 쉬움보다는 단순함이 더 중요하다.
리팩터링에 대한 투자는 기업 입장에서도 이익이 증가한다. 이건 실험과 논문으로 입증되었다.
대규모 시스템은 작은 컴포넌트로 나누어 관리하면 복잡도가 줄어든다는건… 이미 증명된 사실이다.
하지만 너무 많은 계층과 추상화에대한 너무 많은 규약에 의해 오버헤드가 발생하기도 한다.
마이크로서비스 기반 아키텍트를 이용하면 이런 계층 추상화에 많은 도움이 된다.
모든것은 최대한 단순하게 유지해야한다.
제거 할수 있는 우발적 복잡성이 있지만, 어쩔 수 없는 본질적인 복잡성도 있다는걸 알아야 한다. 그런 경우는 숨기려고 하지말고 복잡성을 위해 복잡성이 있을만한 적절한 위치를 제공하자.
서드파티 모듈은 의존성에의한 추가적인 복잡성을 초래할 수 있다.
마이크로아키텍처 또한 서비스간에 관계를 유지하기 위한 추가적인 복잡성이 있을 수 있다.
복잡성을 완전히 해결할 수 없으며 정답도 없다. 책임있는 개발자가 항상 노력해야하는 과제이다.
모듈성
대규모 앱을 구축하는 핵심 원칙은 모듈화하와 컴포넌트화에 있다.
모든것을 독립적으로 테스트 할 수 있는 작은 조각으로 만들어 관리하는 것이다.
프로그램의 규모가 커질수록 모듈성은 더 중요해진다.
특히 리액트에서는 컴포넌트 단위로 모듈화가 적용된다.
어플리케이션을 작은 컴포넌트로 잘게 나누고 거기서 재활용 가능한 컴포넌트를 식별하라.
너무 잘게 나눠도 방해가 될수 있다. 나누는 기준은 재사용성 가독성 단순성 독립된 테스트 등을 기준으로 삼으면 도움이 된다.
재사용 가능한 컴포넌트와 스타일 가이드라인의 집합인 디자인 시스템으로 활용하라.
리액트의 lazyf로딩과 동적 import, suspense 기능을 적극 활용하라.
코드스플리팅 이란 앱을 구성하는 큰 코드덩어리를 관리가능한 덩어리로 나누는 기법을 말한다.
컴포넌트를 독립적으로 구현하면 필요할 때마다 코드 청크를 불러올 수 있도록 코드스플리팅을이 더 효율적으로 동작하게 하기 용이해진다.
코드분할은 전략은 주로 아래 3가지와 같은 패턴으로 사용한다.
- 라우터에 따라분할
- 컴포넌트별로분할
- 사용자 인터렉션에 따른분할
앤트리 포인트 분할은 라우팅에 따른 페이지 븐할에 전략을 의미한다.
밴더분할은 서드파티 의존성을 청크에서 분리하는 전략이다(브라우저 캐싱전략에 유리)
동적분할은 사용자 인터렉션에 따른 컴포넌트 동적 로딩 전략을 말한다.
공격적에게 세세하게 나누는 방법도 있지만 브라우저 성능이 코드 압축 면에서 트레이드 오프가 있어서. 오히려 안좋을 수도 있다.
성능
사용자인터페이스 -> 브라우저엔진(데이터저장소) -> 렌다링 엔진 (네트워킹, 자바스크립트, ui백엔드)
렌더링
브라우저는 문서 문자열을 바탕으로 DOM, CSSOM을 생성한다. 이걸 합쳐 렌더트리를 구축한 후 사용자 화면에 표현한다.
표현은 레이아웃-> 페인트를 거친다. 브라우저에서 레이아웃을 표현하는데는 많은 비용이 든다.
로딩
프리패칭, 프리로딩을 속성을 이용해 브라우저에게 힌트를 주는 방식으로 로딩속도 최적화 가능.
성능병목 해결하기
크롬 브라우저 데브틀로 병목구간을 확인할 수 있다.
https://developer.chrome.com/blog/inside-browser-part1?hl=ko
https://www.aladin.co.kr/m/mproduct.aspx?itemid=371722160
리소스의 양이 너무 크면 다운로드 속도가 느려지므로, 청크를 작게 나누거냐 용량을 작게 유지하려는 노력이 필요하다.
50-100kb 가 가장 적당하며 더 크면 나눠야한다.
메인 스레드를 차단시키는 킨 태스크를 피하고 인라인에 포함시키는 스크립트도 피하라.
1kb가 넘는 스크립트는 인라인으로 작성하지 마라.
상호작용 최적화
INP는 상호작용의 속도에 대한 측정 지표이다.
리액트의 useTransition 같은 컨컬런트 api같은걸 이용해 상호작용을 개선할 수 있다.
넥스트js 엡 라우터는 기본적으로 startTranditon 을 이용해 전환한다.
네트워킹
모던 브라우저들은 HTTP/3를 기본적으로 지원한다. HTTP/3는 기존 프로토콜 대비 성능과 보안이 개선되었으며, 멀티플렉싱 스트림을 지원한다.
멀티플렉싱 스트림(멀티플렉싱)이란 하나의 연결 내에서 여러 요청과 응답을 동시에 처리하는 방식으로, 네트워크 효율과 응답 성능을 향상시킨다.
특히 HTTP/3 환경에서는 응답을 문서 전체가 완성된 뒤 한 번에 전송하는 것이 아니라, 스트리밍 방식으로 순차적으로 전송할 수 있다. 이를 통해 사용자는 콘텐츠를 더 빠르게 확인할 수 있어 체감 성능이 크게 개선된다.
이러한 스트리밍 렌더링 방식은 Next.js와 같은 프레임워크에서 renderToStream(Streaming SSR)과 같은 API를 통해 실용적으로 활용되고 있다.
서드파티 의존성의 영향 줄이기
웹 개발툴로 느리게 로드되는 의존성을 식별하고 대체하는 전략이 필요하다.
cdn울 활용하고 더블키 캐싱기법을 활용하면 성능에 도움이 됨
더블키 캐싱은 URL 하나만으로 캐시하지 않고, 추가 조건을 함께 키로 사용해 캐시를 분리하는 전략이다.
번들러 애널라이저로 크기를 많이처지하는 라이브러리 판별가능하다.
렌더링 전략
csr, ssr, spa, spa, ssg, isr, 스트리밍 ssr, 재개 가능성, 엣지 랜더링(cdn 전략을 말함)
등이 있으며 프레임웍이 여러모드를 지원하는 경우도 있고, 특정 프레임웍이 특정모드에 최적화된 경우도 있다.
인지 성능 최적화.
예를 들면 스켈레톤 기법이 잘 알려진 최적화다.
오히려 산만한 사용자 경험이 될 수 있으므로 주의해야 한다.
그 밖에도 서비스워커를 이용한 캐싱, stream api, fetch api를 이용한 스트리밍 요청 swr을 알아두면 좋다.
클라이언트힌트나 적응적 로딩을 활용하면 기기별로 최적화된 서비스를 제공할 수 있다.
클라이언트 힌트는 브라우저가 자신의 실행 환경을 서버에 전달하는 메커니즘이며, 적응적 로딩은 이 정보를 바탕으로 기기와 네트워크 상황에 맞게 리소스와 렌더링 전략을 조절하는 접근 방식이다.
Css 컨텐츠 비지빌리티 속성이나, 인터렉션 api를 이용하면 성능 향상에 도움이 된다.
디자인 시스템
재사용 가능한 컴포넌트 에셋, 가이드라인의 집합.
css 코딩 그타일 BEM
<div class="card card--featured">
<h2 class="card__title">상품 이름</h2>
<p class="card__desc">설명 텍스트</p>
<button class="card__button card__button--primary">구매하기</button>
</div>
css 코딩 스타일 프레임웍 tailwind css
디자인 토큰
디자인 시스템에서 색상, 폰트, 간격, 그림자, 라운드 값 같은 UI 스타일 값을 변수처럼 관리하는 방식
디자인 시스템에서 디자인의 업데이트를 단순화 할수 있다.
예전에는sass 같은 슈퍼셋을 이용했으나 요즘에는 css 속성 값으로 사용할수도 있다.
컴포넌트 라이브러리
브랜드 스타일이 내장된 재사용 가능한 UI 컴포넌트 세트 - fsd 의 쉐어드 계층에 속함.
접근성
시멘틱 웹을 고려해서 구현해야 한다.
성능
너무 리소스는 분할하면 성능이 향상되고 너무 작은 리소스들은 합쳐야 성능이 향상된다. 스프라이트 이미지 기법같은걸 적절히 활용하면 좋다(Http요청을 줄여야 하므로)
문서화
스토리북과 같으누도구로 디자인시스템의 체계화된 문서르루제공할수 있다.
사례
에어비엔비 dls나 머터리얼 디자인이 디자인시스템 구축의ㅜ좋은 예이다. 에어비엔비 dls나 머터리얼 디자인이 디자인 시스템 구축의 좋은 예이다.
데이터 가져오기
fetch와 axios의 에러처리와 이벤트 핸들러 처리등 보일러 플레이트가 많은 단점이 있다.
리액트쿼리
상태표시, 캐싱, 캐싱업데이트 등의 기능지원.
staleTime 옵션으로 캐싱 유효시간 지정 가능
데이터 업데이트 요청 후 useMutation으로 캐시 강제 업데이트 가능
리액트쿼리의 프로패칭 api를 사용해서, 사용자 경혐을 개선할수도 있다.
재시도 매커니즘
3번 쿼리 실패 후 에러가 표시됨.
재시도 유무나 횟수는 옵션으로 설정가능.
강력한 데브툴 제공.
캐쉬조작및 수덩 가져오기 기능 제공.
리액트쿼리 최적화 팁
- 데이터모델을 세심하계 설계해라.
데이터 소스의 수와 데이터 저장 횟수는 데이터모델에 따라 틀려지기때문에 백엔드 데이터 모델이 중요하다.
- 엔드포인트를 최적화하라.
-
데이터베이스 호출 최소화
-
인덱스효울 최적화, 서버응답 압축
-
가능하다면 여러 요청을 하나의 요청으로 배치 처리.
-
2개의 요청이 항상 같이 쓰인다면 하나로 줄여라.
-
즉시 필요하지 않은 데이터는 지연로딩 적용.
-
캐싱을 적극적으로ㅜ사용하여 요청을 줄여라.
Graphql을 사용하면 꼭 필요한 데이터만 용량 최적화하여 가져올 수 있다. 하지만 올바르지 않게 사용하면 복잡성만 커지는걸 주의해야 한다.
데이터독과 같은 서비스를 사용하여 성능 모니터링 및 최적화
7. 상태관리
컴포넌트는 상태를 반영하는 내부 메커니즘이 있다.
리액트에서는 useState를 이용해 이를 구현한다.
Props라는 개념을 통해 상태가 하향식으로 흐른다. 컴포넌트들은 트리구조를 통해 연결되어 있다.
하위 컴포넌트에서 상위의 상태를 참조할 때는 콜백을 통해서 관리한다.
컨텍스트 api는 prop 드릴링이라는 문제를 해결한다.
useReducer를 사용하면 대규모의 상태를 예층 가능한 방식으로 사용할수 있다.
내부 상태관리로만은 불편할 때, 리덕스와 같은 상태 관리툴을 사용할 수 있다.
효과작인 상태관리를 하기위하누고려사항
- 데이터 가져오기 부터 고려하라.
리액트쿼리, 아폴로, swr등 을 활용하라.
다른 전용 상태관리툴은 필요없을 수도있다.
- 단순한 상태관리툴의 장점을 평가하라
복잡한 툴이 사실 불필요할 수 있다.
- 되도록이면 컴포넌트 수준에서 상태를 유지하라.
8. 국제화
인터널라이제이션 (i18n) 국제화를 뜻한다.
코드에서 텍스트와 컨텐츠를 분리하라
사드파티 지역화 라이브러리를 사용하라.
동적로딩
동적로딩을 사용하면 필요한 다국어 리소스만 필요한 순간에 로드할 수 있다.
복수의 표현을 가진 언어
ICU문법을 통해 지원 가능
{count, plural,
=0 {No messages}
one {# message}
other {# messages}
}
리액트에서는 FormatedMassage 컴포넌트 지원
자바스크립트의 INTL객체가 rㅜㄱ가별 날짜 시간 숫자 형식 변환을 지원한다.
react-intl 에서도 이런 날짜 형식 변환 컴포넌트를 지원한다.
오른쪽에서 왼쪽으로 쓰는 언어를 고려하라.
html에서 dir 속성을 통해 지원한다.
국가별로 다른 폰트를 지원하도록 css처리 하면 좋다.
아이콘 뒤에 텍스트가 올때는 보통 ltr언어에서는 marget-right같은 속성을 주면 되지만 rtl언어에서는 깨질수 있다. 이럴때는 margin-inline-end 같은 css 속성을 활용하면 편리하다.
9. 코드 조직화하기
폴더 파일구조
전통적인 방식으로
components, pages, hooks, services, store, utils 처럼 구성할 수 있다.
또 feature 단위로도 구성할수 있으며, 요즘은 fsd 방법론이 유행이다.
배럴 익스포트
/components
Button.js
Modal.js
Card.js
index.js ← barrel
익스포트용 index.js
export { default as Button } from './Button';
export { default as Modal } from './Modal';
export { default as Card } from './Card';
깔끔한 사용
import { Button, Modal, Card } from './components';
(사용자 실수가 많을것고 남용하면 헷갈릴것 같아서 개인적으로는 마음에 드는 방식이 아님)
프리젠테에션 로직과 데이터 가져오기 로직을 분리하라
css 방법론을 잘 활용하라. 글로벌 css 규칙을 피하라
테스트 작성
테스트코드는 리그레션 버그를 방지한다.
문서화
주석 이나 리드미 작성
정기적 리팩토링
코드리뷰 활용하여 개선
10. 개인화와 A/B 테스팅
데이터를 기반으로 사용자 경험을 최적화 하는 작업이 개인화다.
두가지 버전으로 테스트를 해서 효과적인 버전을 확정하는걱 A/B테스트다.
사용자 피드백 수집 및 행동 추적이 포함된다.
리책트의 컨텍스트api를 활용하면 특정 컴포넌트 영역에 개인화를 세팅할 수 있어 편리하다.
stat-sig라는 툴이 있으며 리액트 컴포넌트로 이런 기능들을 제공한다.
기능 플래스
기능플래그를 통해 특정 기능을 한번에 배포하지 말고 시장의 반응이나 트래픽을 가늠해 보며 점진적으로 배포할 수 있다.
직접 구현할 수도 있지만 stat-sig ㅌ은 툴을 쓰는걸 태기업들은 선호한다.
11. 확장 가능한 웹아키텍처
필요할 때 리소르를 추가하거나 제거하여 다양한 워크로드를 처리할수 있다.
로드밸런서
유입되는 트래픽을 여러 서버로 분산해준다.
로드 밸런싱 알고리즘 헬스체크 자동 스케일링이 고려되어야 한다.
클라우드 서비스들은 이런 기능을 단순화하여 서비스로 제공한다.
캐싱
브라우저 캐싱, cdn 캐싱, 인메모리 캐싱
클라우드 서비스에서도 다양한 관리형 캐시서비스를 제공한다.
CDN은 사용자와 가장 가까운 서버에서 테이터를 제공한다.
수평적 확장
특히 웹서비스와 같은 경우에 서버를 늘리거나 인스턴스를 늘리는걸 수평적 확장이라고 한다.
수직정 확장
서버 하나의 파워나 메모리 추가 또는 어플리케이션을 고효율 알고리즘으로 교체하는걸 수직적 확장이라고 한다.
마이크로서비스
어플레케이션을 작고 독립적으로 배포할 수 있게 세분화 하여 서비스에하는 접근법을 말한다.
디테일하게 관리할 수 있는 좋은 전력이지만 그 자체로 복잡성이 있다.
도커 쿠버 어플레케이션 서비스, 디스커버리와 같은 서비스를 클라우드 서비스에서 제공한다.
확장 가능한 애플리케이션 특성
성능 스캐일링 비용에 효율적이어야 한다.
도커
도커 컨테이너 어플레케이션과 환경 그리고 의존성을 함께 패키징하여 생성 배포 실행 프로세스를 단순화 한다.
쿠버네티스
컨테이너 오케스트레이션 플랫폼.
컨테이너의 배포 확장 관리를 자동화한다.
12. 테스팅
테스팅은 코드에 관한것 뿐만 아니라 곧 개발문화다.
테스트에 관한 응집된 도구셋을 가져야 한다.
태스트 방법은 다양하기 보다는 통일하는게 좋고 통일된 문화규칙을 만드는게 좋다.
- 단위테스트
Jest와 테사팅하이브러리에 대한 설명
-
E2E 테스트
-
통합테스트
단위테스트와 틀린점은, api 모킹 같은 다른 요소와의 상호작용을 염두해 둔다는 것이다.
- 스냅숏테스트
Jest react-test-renderer 를 이용해 스탭샷 테스트 가능
제스트 명령어로 스탭샷 업데이트 가능
어플리케이션을 어떻게 테스트해야 되는가
스냅샷 테스트는 복잡한 시각적 요소를 가진 컴포넌트 테스트에 유리하다.
하지만 스탭샷 테스트는 최소화 하는게 좋고, 보조 수단으로만 이용하는게 좋다.
테스트 커버리지는 100프로일 필요는 없지만 5-60프로의 높은 커버리지를 유지하는게 좋다.
단위테스트가 가장 많아야 하는 피라미드 방식이 대세였지만 요즘은 통합테스트 위주로 작성하는게 효율적임이 증명되어 대세로 자리잡고 있다.
13. 툴링
버전관리는 깃
Sonarquve같은 프로그램을 깃 워크플로우에ㅜ텅합하면 코드의 보아누취약점으루예방할수있다
번들러 ci툴 설명
로깅과 성능 모니터링
버그를 식별하고 수정.
datadog sentry 같은 중앙집중식툴 사용권장
로그에 버전과 그사용자 아이디 등을 남기면 큰 도움이 된다.
splunk 같은 툴을 사용하면 성능도 모니터링 할 수 있다.ㅓ
14. 기술적 마이그레이션
의존성 라이브러리 교체, 버전 업 이나 플랫폼 변경과 같은 작업을 포함한다. 아키텍처 업데이트
좋은마이그레이션
애초부터 새로운 기술스택으로 새롭게 작성.
빠른마이그레이션
앱을 조각조각 나누고 부분적으로 점진적 마이그레이션 한다.
교살자 어플리케이션
빠른마이그래이션과 비슷하지만 이 패턴은 라우팅 증심으로 교체하는 방식이다.
라이브리드 접근법
상황에따라 여러가지 마이그레이션 전략을 조합.
마이그레이션 범위를 이해 -> 계획 -> 실행
코드모드 활용
특정 패턴의 정형화된 코드 마이그레이션은 스크립트로 일괄 수정할수도 있다.
15. 타입스크립트
리액트의 children은 숫자 문자 같은 기본값 이나 리액트 요소, 그리고 이것들을 포함하는 배열이나 Fragement로 표현되며 통틀어서 React.ReactNode 타입으로 표현할수 있다.
React.ReactElement 는 태그로 표현되는 jsx 요소이며 좀더 ReactNode에 비해 좀더 구체적일때 사용할수 있다.
객체에 as const 를 사용하여 enum을 만들 수 있다.
react typescript cheatsheet를 읽어볼것
.d.ts 선언파일들의 위치는 tsconfig 에 include 옵션에 넣어주면 된다.
Graphql은 타입제너레이터가 있어서 타입스크립트에서 그대로 사용가능하다.
rest api도 open ai와 같은 명세로 기술되어 있다면 타입스크립트 타입으로 쉽게 변환할 수 있다.
서버와 클라이언트의 양방향 통신 프로토콜 grpc도 타입스크립트를 위한 ts-proto 도구를 지원한다.
Jsdoc 타입 주석이 있으면 자바스크립트 프로젝트를 타입스크립트로 마이그래이션 할때 편리하다.
16. 라우팅
spa를 이용한 라우팅은 사용자에게 매끄러운 경험을 제공한다.
리액트 라우터
리액트에서는 리액트라우터를 이용해 라우팅을 제공한다. useParams, useLoader 와 같은 api를 이용가능.
lazy속성을 이용해 지연로딩 가능. 래이지 로딩. 컴포넌트는 로더와 에러바운더리를 독립적으로 구현가능.
nextjs 앱라우터
앱라우터는 파일시스템 기반의 라우터를 제공한다.
Nextjs는 앱라우터 지원과 함께 서버컴포넌트를 전면 지원한다.
API 라우터를 지원하면 라우터에 따라 코딩 자동분할이 지원된다.
서버컴포넌트를 기본으로 사용하며, use client 키워드를 통해 클라이언트 컴포넌트를 지정할 수 있다.
서버컴포넌트를 사용하면 컴포넌트에서 직접 데이터를 가져올수 있다.
loading.js 와 error.js 를 제공하여 에러나 로딩에 대한 상태를 제공한다.
사용자 중심 api 디자인
효과적이고 가용자 친환경 적인 api를만들기 위한 가이드
잘 장의된 api디자인으누효울적인 개발과 강건한 애플리케이션으루만들 수 있게 한다.
일관성
일관성 있는 api는 개발자의 인지부하를 줄인다.
명확한 설명적 이름을 사용하라.
소문자와 하이픈만 사용하라.
리소스 이름에는 명사를 사용하라.
명확한 crud가 아닌 api에는 동사를 사용하라
이름에 단수 복수를 일관성있게 설계하라.
Url을 가능한 짧게 유지하라.
필터링 정렬 페이징을 위해 매개변수를 사용하라.
유사한 리소스에 대해 일관성 있는 패턴을 사용하라.
모든 응답에 대해 일관된 구조를 사용하라.
에러 핸들링
적절한 http 상태코드르류사용하라. 2xx , 4xx , 5xx
상세한 에러메시지를 제공하라.
고유한 에러코드를 사용하라.
에러가 여러개일 때는 에러를 한번에 전달하라.
디버깅을 위해 에러를 기록하라(중요한 로그를 잘 쌓으라는 이야기인듯)
문서화
명확한 개요를 작성하라.
상세한 엔드포인트 문서.
문서에 코드 샘플을 포함하라.
인증에 관해 설명하라.
버저닝
url 버저닝은 url에 버전 정보를 포함한다(단점으로는 사소한 변경에 덜 유연하다)
쿼리 매개변수 버저닝
개발자들이 모르고 지나칠 수 있다. 캐싱전략이 복잡해진다.
헤더 버저닝
서버에서 커스텀 헤더 처리해야하며, 단점으로는 헤더같은 정보는 눈에 잘 안띄므로 api 사용자가 간과하거나 인지하지 못할 수 있다.
보안
인증과 허가는 api 보안의 기본 요소이다.
Api 키, oauth2, json 웹토큰 jwt
사용자가 아이디 패스워드를 넣어서 인증하고 jwt를 전달받는다.-> 사용자가 발급받은 토큰을 사용허여 api 요청을 보낸다.
CSP(Content-Security-Policy)
브라우저가 HTML 문서를 렌더링할 때, 어떤 스크립트·스타일·이미지·리소스를 어디에서 불러와도 되는지 결정하는 보안 규칙. 즉, XSS·스크립트 인젝션을 막기 위해 브라우저에게 허용된 리소스만 실행하도록 강제하는 정책.
HTML문서에서만 작동API(JSON)에는 적용되지 않음- 외부 스크립트/인라인 스크립트 차단 가능
nonce/hash기반 허용 가능
레이트 제한 (Rate Limiting)
클라이언트가 일정 시간 동안 API에 보낼 수 있는 요청 수를 제한해, 서버 과부하, DDoS, 무차별 대입 공격을 방지하는 보안·성능 메커니즘.
-
예
- “1분에 100 요청까지만 허용”
- “IP당 초당 10회 이상 요청 금지”
-
효과
- 서버 보호
- 악성 트래픽 차단
- 과도한 API 사용 방지
18. 리액트의 미래
useTransection은 시간이 걸릴수 있는 동작을 정의한다. fetch api같은… isPending 상태를 제공하므로 유용하다.
useActionState— 사용 방법(React 19+)
useActionState는 서버 액션 또는 비동기 액션의 상태(pending/error/result)를 자동 관리해주는 훅.
const [state, dispatch, isPending] = useActionState(actionFn, initialState);
-
파라미터
- actionFn — 실행할 액션(보통 async 함수 또는 서버 액션)
- initialState — 첫 상태 값
-
반환값
- state — 액션 결과(state)
- dispatch — 액션을 실행하는 함수
- isPending — 실행 중 여부(boolean)
-
기본 예시
async function add(formData) {
const value = Number(formData.get("value"));
return value + 1;
}
const [result, submit, pending] = useActionState(add, 0);
return (
<form action={submit}>
<input name="value" type="number" />
<button disabled={pending}>Add</button>
<p>Result: {result}</p>
</form>
);
- 특징 요약
- 비동기 로직 실행 → state 자동 업데이트
- pending 상태 자동 관리
- form action과 자연스럽게 연동됨
- React 19 전용 기능
useFormState도 비슷하누기능을 제공하는데. useFormState 훅은 꼭 Form을 사용해야 한다.
useOptimistic (낙관적 업데이트)
useOptimistic은 UI를 먼저 빠르게 바꾸지만, 서버 액션이 실패하면 자동으로 원래 상태로 롤백되는 낙관적 UI 훅이다.
**useOptimistic의 핵심은 “성공하면 그대로, 실패하면 되돌리는(rollback) UI”**를 만드는 것.
const [likes, setLikes] = useState(0);
const [optimisticLikes, addOptimistic] = useOptimistic( likes, (current, inc) => current + inc );
async function likeAction() {
addOptimistic(1); // UI 즉시 증가
try {
await saveToServer(); // 서버 호출
setLikes((v) => v + 1); // 성공 → 진짜 상태 반영
} catch {
// 실패하면 setLikes가 실행되지 않으므로
// optimisticLikes는 자동으로 원래 likes 상태로 복구됨
}
}
use() (React 19)
use()는 컴포넌트 안에서 Promise(비동기 데이터)나 Context 값을 즉시 읽을 수 있게 해주는 React 19의 새 API로, 렌더링 중에 await처럼 동작하며 조건문·반복문 등 어디서나 사용할 수 있다.
리액트 컴파일러
무엇을 메모화할지에대한 의사결정을 자동화 한다.
리액트 컴파일러는 외부함수조차도 메모화한다. 자동으로.
하지만 아직까지 useEffect 내에서 사용되는 값에 대해 메모제이션을 잘 해내지 못한다.
리액트 컴파일러는 컴포넌트가 멱등성을 가지며 순수해야 정상 동작하며 props가 불변셩을 유지하는등 이상적인 컴포넌트 상태라고 가정하여 컴파일러가 관여하게된다.
따라서 독특하고 개성적 방법으로 React를 사용하는 경우 컴파일러가 제대로 작동하지 않을 수 있다.
리액트 서버컴포넌트
서버 컴포넌트에서 가져온 데이터를 suspense에 쌓인 클라이언트 컴포넌트에서 use()로 받아 사용할수 있다.
서버액션
클라이언트 사이드에서 서버 함수를 직접 사용할수 있다.
서버컴포넌트에서 정의한 함수를 클라이언트 컴포넌트에 prop으로 념겨서 사용 가능하다.
서버 액션 함수 안에는 use action 지시어를 써줘야 한다.
서버 컴포넌트가 유용하지만 꼭 사용해야 되는건 아니다.