API 호출 문제
따라서 당근에서는 SSR 웹앱을 도입함.
CSR 기반 서비스는 Application Server가 없이 브라우저에서 직접 API서버에 API요청을 하기에 크로스 도메인 환경이다.
이때 발생하는 CORS 관련 preflight 요청이 100-200ms 지연을 일으킴
API를 여러 개 부르면 이 지연이 큰 지연을 부름
SSR은 비어있는 div에 브라우저가 모든 준비가 끝난 후 렌더링하는 것이 아닌 서버에서 HTML을 미리 만들어 내려주기 때문에 CSR 대비 화면이 빠르게 렌더링 되는 것처럼 느껴짐.
Streaming SSR: 기존 SSR이 화면을 한번에 그리기 때문에 API 호출 때문에 지연됐다면, 스트리밍 방식은 화면을 나누어서 그리는 방식. 따라서 API 호출과 관련 없는 부분이라면 가장 빠르게 그릴 수 있다.
<Suspense> 경계 밖 부분을 셸onShellReady 콜백이 실행되고, pipe 함수를 호출해서 스트리밍 시작.React Native에서는 키보드가 나타날 때 화면이 자동으로 위로 밀리는 현상이 발생해 입력창이 가려지는 문제가 생깁니다. 이를 해결하기 위해 KeyboardAvoidingView를 사용합니다.
padding을 주고,height를 줄여줍니다.import { KeyboardAvoidingView, Platform } from 'react-native';
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
{/* Input이 포함된 뷰 */}
</KeyboardAvoidingView>
iOS는 기본적으로 UIScrollView의 바운스(Bounce) 기능이 켜져 있어 웹뷰에서 pull-to-refresh가 스크롤 동작과 충돌합니다.
특히 WKWebView를 사용할 경우, 내부적으로 스크롤 가능한 영역이 있어 스크롤 중인데도 리프레시가 시도되는 문제가 발생합니다.
overflow: hidden을 주고,/* WebView root */
body, html {
overflow: hidden;
height: 100%;
}
/* 내부 스크롤 영역 */
.scrollable-container {
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
한글 입력 시 이전 글자가 버퍼에 저장되어 다음 입력에 영향을 미치는 이슈가 있습니다. iOS는 아직 한글 입력 처리에서 특이한 동작을 보이는데, 특히 useRef로 연결한 input에서 이 문제가 재현됩니다.
opacity: 0)을 하나 만들고,useEffect(() => {
if (isAddingRoutine && inputRef.current) {
fakeInputRef.current?.focus(); // 가짜 input 먼저
inputRef.current.focus(); // 진짜 input에 focus
}
}, [isAddingRoutine]);
전체 컴포넌트 예시는 다음과 같다:
// 중략 - AddRoutineItem 컴포넌트 전체 코드 (본문 참고)
웹뷰 기반 앱이라면 권한 요청도 앱과 웹 간 메시지 처리로 나눠야 합니다.
graph TD
A[앱 최초 실행] --> B[앱에서 권한 안내 페이지 노출]
B --> C[웹에서 권한 상태 확인]
C --> D{권한 상태}
D -->|undetermined| E[권한 팝업 요청]
D -->|granted| F[기능 실행]
D -->|denied| G[설정페이지 이동 안내]
G --> H[AppState 통해 권한 재확인]
React Native에서는 AppState를 활용해 설정 → 앱 복귀 시 권한 상태를 다시 확인할 수 있습니다.
import { AppState } from 'react-native';
useEffect(() => {
const listener = AppState.addEventListener('change', (state) => {
if (state === 'active') {
checkPermissionAgain();
}
});
return () => listener.remove();
}, []);