싱글 페이지 애플리케이션이란
렌더링과 라우팅에 필요한 대부분의 기능을 서버가 아닌 브라우저의 자바스크립트에 의존하는 방식을 의미한다
첫 데이터에서 데이터를 모두 불러온 이후로 페이지 전환을 위한 모든 작업이
자바스크립트와 브라오저의 history.pushState, history.replaceState 로 이루어지기 때문에
페이지를 불러온 이후에는 하나의 페이지에서 모든 작업을 처리하게 된다
body 내부에 아무런 내용이 없다
body 내부의 모든 내용을 자바스크립트 코드로 삽입한 후 렌더링 하기 때ㅜㅁㄴ이다
새로운 페이지를 새로 요청하지 않고 다음 페이지의 렌더링에 필요한 정보만 http 요청 등으로 가져온 뒤 DOM 을 추가, 수정, 삭제한다
서버 사이드에서 작동하던 방식의 애플리케이션은 페이지 전환이 발생할 때마다 새롭게 페이지를 요청하고 HTML 페이지를 다운로드해 파싱하는 작업을 매번 거친다
페이지를 처음부터 새로 그리기 때문에 페이지 전환이 부드럽지 못하다
싱글 페이지의 장점을 발판으로 근래에는 많은 웹페이지들이 싱글 페이지 렌더링 방식을 채택했다
자바스크립트가 다양한 작업을 수행하게 되면서 자바스크립트를 모듈화하는 방안이 논의됐고 그에 따라 등장한 것이 CommonJS 와 AMD(Asynchronous Module Definition)다
이후 Backbone.js 와 Angular.js, Knockout.js 등이 등장하면서
자바스크립트 수준에서 MVx 프레임워크를 구현하기 시작했다
서버에서만 할 수 있는 복잡한 작업도 자바스크립트가 할 수 있게 된 것
이후 React, Vue, Angular 의 시대가 온다
PHP 시절에는 자바스크립트 이외에도 신경 쓸 것이 많았지만
이러한 발전으로 자바스크립트만 잘 작성하면 프론트 개발에 문제가 없게 되었다
이러한 싱글 페이지 애플리케이션의 유행으로 생겨난 용어가 JAM 스택이다
기존의 웹 개발은 LAMP 스택으로 Linux, Apache, MySQL, PHP/Python 으로 구성되어 있었다
JavaScript, API Markip 스택이 잼스택
잼스택의 인기와 더불어 Node.js 고도화에 힘입어 MEAN, MERN 스택처럼 서버도 자바스크립트로 구현하는 구조가 인기를 끌기 시작했다
싱글 페이지 애플리케이션이 사용자에게 일정 부분의 부담을 전가시키는 것이
기기와 인터넷 환경이 발전할 것이기 때문에 괜찮을 것이라고 생각했다
그러나 웹 전반을 이루는 환경이 크게 개선됐음에도 과거와 비슷하거나 더 느리다
싱글 페이지 애플리케이션의 단점을 보완하고자 다시 서버 사이드 렌더링이 떠오르고 있다
양 쪽 모두 알고있어야 하는 시대가 왔다
서버 사이드 렌더링 역시 만능이 아니고
싱글 페이지 애플리케이션의 한계점도 명확하다
서버 사이드 렌더링 방식을 멀티 페이지 애플리케이션이라고도 부른다
이러한 기법은 모두 싱글 페이지 애플리케이션에서 구현 가능하지만 완벽하게 구현하기는 어렵다
현대의 서버 사이드 렌더링은 지금까지 LAMP 스택에서 표현했던 방식돠는 조금 다르다
기존 방식은 라우팅이 발생할 때도 서버에 의존하지만 요즘에는 싱글 페이지 애플리케이션 방식으로 작동한다
양 쪽의 장점만 취한 방식으로 발전
Next.js, Remix 등이 이에 해당한다
리액트는 기본적으로 프론트엔드 라이브러리다
브라우저 환경에서 렌더링 할 수 있는 방법을 제공하지만
리액트 애플리케이션을 서버에서 렌더링할 수 있는 API 도 제공한다
이런 API 는 서버 환경에서만 실행할 수 있다
리액트 저장소의 react-dom/server.js 에 API 가 작성되어 있다
22년 8월 기준으로 server.node.js 에 있는 함수를 export 하고 있다
리액트 18부터 react-dom/server 에 renderToPipeableStream 이 추가되었다
인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자로 반환하는 함수다
서버 사이드 렌더링을 구현하는 가장 기초적인 API
최초 페이지를 HTML 로 렌더링하는 역할을 한다
renderToString 을 활용해 리액트 컴포넌트를 HTML 문자열로 만든다
impoert ReactDomServer from 'react-dom/server'
function ChildrenComponent({ fruits }: { fruits: Array<string> }) {
useEffect(() => {
console.log(fruits)
}, [fruits])
function handleClick() {
console.log('hello')
}
return (
<ul>
{fruits.map((fruit) => {
<li key={fruit} onClick={handleClick}>
{fruit}
</li>
})}
</ul>
)
}
function SampleComponent() {
return (
<>
<div>hello</div>
<ChildenComponent fruits={['apple', 'banana', 'peach']} />
</>
)
}
const result = ReactDOMServer.renderToString(
React.createElement('div', { id: 'root' }, <SampleComponent />),
)
위 코드는 아래와 같은 결과를 만들어낸다
<div id={'root'} data-reactroot=''>
<div>hello</div>
<ul>
<li>'apple'</li>
<li>'banana'</li>
<li>'peach'</li>
</ul>
</div>
SampleComponent 와 ChildrenComponent 는 일반적인 리액트 컴포너트이고
ReactDOMServer.renderToString 으로 부모 컴포넌트인 SampleComponent 를 렌더링한다
useEffect 나 handleClick 같은 요소는 결과물에 포함되지 않는다
renderToString 은 인수로 주어진 리액트 컴포넌트를 브라우저가 렌더링 할 수 있는 HTML 을 빠르게 제공하는데 목적이 있기 때문이다
필요한 자바스크립트는 별도로 브라우저에 제공해야 한다
data-reactroot 속성은 리액트 컴포넌트의 루트 엘리먼트가 무엇인지 식별하는 기준이 된다
renderToString 과 유사한 함수다
두 함수 모두 리액트 컴포넌트를 기준으로 HTML 문자열을 만든다는 점에서 동일하다
차이점은 reactroot 와 같은 리액트에서만 사용하는 추가적인 DOM 속성을 만들지 않는다는 점이다
리액트에서만 사용하는 속성을 제거하면 결과물의 HTML 크기를 약간 줄일 수 있다
대신 리액트에서 제공하는 useEffect 와 같은 브라우저 API 를 사용할 수 없다
r
renderToString 과 결과물은 동일하지만 두 가지 차이점을 같는다
renderToString 과 renderToStaticMarkup 은 브라우저에서 실행할 수 는 있지만
renderToNodeStream 은 브라우저에서 실행할 수 없다
renderToString 과 renderToStaticMarkup 도 브라우저에서 실행할 이유는 없다
두 번째 차이점은 결과물의 타입이다
renderToNodeStream 의 결과물은 ReadableStream 이다
utf-8 로 인고팅된 바이트 스트림으로 Node.js 나 Deno, Bun 같은 서버 환경에서만 사용할 수 있다
string 을 얻기 위해서는 추가적인 작업이 필요하다
Stream 은 데이터를 가져올 때 청크로 분할해 조금씩 가져오는 방식을 의미한다
결과물이 큰 경우에 서버에서 한 번에 큰 데이터를 가져오는 것은 부담이다
renderToNodeStream 청크로 분리하여 순차적으로 처리할 수 있다는 장점이 있다
renderToNodeStream 과 결과물은 동일하지만 리액트 속성이 제공되지 않는다
renderToString 과 renderToNodeStream 의 결과물에 자바스크립트 핸들러나
이벤트를 붙이는 역할을 한다
위 두가지의 결과물을 서버에서 렌더링하여 HTML 을 보여주고, hydrate 과정을 통해 정적으로 생성된 HTML 에 이벤트와 핸들러를 붙여 완전한 웹페이지 결과물로 만든다
비슷한 역할을 하는 리액트 메서드 render 는 함수 컴포넌트와 HTML 요소를 인수로 받는다
인수로 받은 HTML 요소에 해당 컴포넌트를 렌더링, 이벤트 핸들러를 붙인다
클라이언트에서만 실행되는 렌더링과 이벤트 핸들러 등 온전한 웹페이지를 만든느 모든 작업을 수행한다
hydrate 도 render 와 유사하다
차이점은 이미 렌더링된 HTML 이 있다는 가정하에 작업이 수행되고 이벤트를 붙이는 작업만 실행한다
서버에서 제공해준 HTML 이 클라이언트의 결과물과 같아야만 hydrate 가 실행된다
hydrate 는 렌더링을 한 번 수행하면서 수행한 렌더링 결과물 HTML 과 인수로 넘겨받은 HTMl 을 비교하는 작업을 수행하기 때문
여기서 HTML 이 기존과 다르다면 렌더링된 HTML 을 사용
이렇게되면 서버사이드 렌더링의 장점을 잃어버리게 된다
시간 함수 같은 것을 서버사이드 쪽에서 사용한다면 이러한 불일치가 계속 일어나게 되니 주의해야 한다
vercel 에서 만드니 풀스택 웹 애플리케이션을 구축하기 위한 리액트 기반 프레임워크
PHP 에서 영감을 받아서 만들었고, PHP 를 대체하기 위해 만들었다고 한다
next.js 기반 프로젝트에서 사용하도록 만들어진 eslint 설정으로 구글과 협업해 만든 핵심 웹 지표에 도움이되는 규칙들이 내장되어 있다
airbnb 규칙과 함께 사용하는 것도 추천한다고 한다
Next.js 는 서버사이드 렌더링을 수행하지만 동시에 싱글페이지 애플리케이션과 같이 클라이언트 라우팅 또한 수행한다
서버사이드 렌더링과 함꼐 프리 렌터링을 지원한다
최초 페이지 렌더링은 서버에서 된다
next 에서 제공하는 클라이언트 라우팅 컴포넌트로 a 태그와 비슷하다
하지만 link 를 사용하면 싱글페이지 애플리케이션처럼 페이지 전환이 일어난다
덕분에 서버사이드 렌더링의 장점과 싱글페이지 애플리케이션의 장점 모두를 살릴 수 있다
window.location.push 대신 router.push 를 사용한다