최근에 나오는 많은 라이브러리, 프레임워크들이 있지만, 프론트엔드 개발자는 근본을 알아야 한다고 생각한다. 제한된 환경에서도 서버 사이드 렌더링(SSR)을 구현하며 유연한 대처를 할 수 있는 능력을 기르기 위해서이다.
바닐라JS
- 순수 자바스크립트로 어떤 라이브러리나 프레임워크를 사용하지 않고 순수 자바스크립트만을 이용하여 웹 개발을 하는 것
SSR
- 서버 측 렌더링을 의미한다. 웹 애플리케이션의 페이지를 서버에서 동적으로 생성하고 렌더링하는 방식
- 참고 : 상황에 따른 렌더링
...
const renderedHTML = await ejs.renderFile(
path.join(__dirname, 'views', 'index.ejs'),
{
videos,
currentPageNo,
headerImageURL,
footerImageURL,
logoImageURL,
},
...
...
else {
currentPageNo = 1;
// eslint-disable-next-line no-shadow
const pageNo = req.query.pageNo || currentPageNo;
const pageSize = req.query.pageSize || 10;
const apiUrl ='주소';
const videoDataResponse = await axios.get(apiUrl);
const videosData = videoDataResponse.data;
const videos = videosData.videos.map((video) => ({
...video,
uploadedAtRelative: dayjs(video.uploadedAt).fromNow(),
uploadedAtOriginal: video.uploadedAt,
}));
const headerImageURL = `${req.protocol}://${req.get('host')}/images/header_logo.webp`;
const footerImageURL = `${req.protocol}://${req.get('host')}/images/footer_logo.webp`;
const logoImageURL = `${req.protocol}://${req.get('host')}/images/logo.webp`;
const renderedHTML = await ejs.renderFile(
path.join(__dirname, 'views', 'index.ejs'),
{
videos,
currentPageNo,
headerImageURL,
footerImageURL,
logoImageURL,
},
);
res.send(renderedHTML);
}
}
...
여기서 포인트는 const renderedHTML = await ejs.renderFile
이부분 부터 시작이 된다.
ejs.renderFile() 함수는 EJS 템플릿 파일을 렌더링하는데 사용된다.
- 첫 번째 매개변수는 렌더링할 템플릿 파일의 경로이다. 이 코드에서는
__dirname/views/index.ejs
파일을 사용한다. __dirname은 현재 스크립트 파일이 위치한 디렉토리를 나타내며,views/index.ejs
는 템플릿 파일의 상대 경로이다.- 두 번째 매개변수는 템플릿에 전달될 데이터 객체입니다. 여기서는 videos, currentPageNo, headerImageURL, footerImageURL, logoImageURL 등을 포함한다. 이 데이터들은 템플릿에서 변수로 사용될 것이다.
- ejs.renderFile() 함수는 Promise를 반환하므로 await 키워드를 사용하여 비동기적으로 실행된다. 템플릿 렌더링이 완료되면 렌더링된 HTML이 renderedHTML 변수에 저장된다. 마지막으로, res.send() 함수를 사용하여 클라이언트에게 렌더링된 HTML을 응답으로 보낸다.
즉, 서버 측에서 EJS 템플릿을 사용하여 동적으로 HTML을 생성하고, 클라이언트로 반환하여 해당 HTML을 표시한다. 이를 통해 서버에서 클라이언트로 동적 콘텐츠를 제공할 수 있다.
...
<% if (videos.length === 0) { %>
<p class="no-list">리스트 없음</p>
<% } else { %>
<ul>
<%
/**
* 서버에서 받아온 비디오 배열 반복
* @param {Array} videos - 비디오 객체들의 배열
*/
videos.forEach(video => { %>
<li>
<a href="/?videoId=<%= video.id %>">
<img src="<%= logoImageURL %>" alt="chan Logo">
<span>🔗 : <%= video.title %></span>
<span>✅: <%= video.uploader %></span>
<span class="uploaded-relative">🗓️: <%= video.uploadedAtRelative %>
<span class="uploaded-original" style="display: none;"><%= video.uploadedAtOriginal %></span>
</span>
</a>
</li>
<% }); %>
</ul>
<% } %>
...
이 코드는 EJS 템플릿을 사용하여 HTML을 동적으로 생성하는 부분이다. 클라이언트에게 반환되어 브라우저에서 렌더링될 HTML 코드가 여기에 포함된다.
즉, 서버에서 받아온 데이터를 템플릿에 채워 동적으로 HTML을 생성하고, 클라이언트에게 반환하여 브라우저에서 렌더링한다.
추가로 템플릿의 <% %>
은 EJS 템플릿 엔진에서 사용되는 구분자로, 이 구분자는 서버 측에서 클라이언트로 HTML을 렌더링할 때 JavaScript 코드를 삽입할 때 사용한다.
네트워크 탭창을 통해 확인을 해보면, 서버에서는 HTML에 내용이 있는 즉, 렌더될 준비가 된 HTML파일을 브라우저가 받아온 것을 확인할 수 있다.
이게 무슨 차이인지 확인을 해보면 대표적인 CSR로 동작하는(최근에는 RSC이라고, 리엑트 서버컴포넌트 기능이 나옴) react의 기본 화면을 통해 분석해보겠다.
You need to enable JavaScript to run this app
이라고 하는데, CSR (Client-Side Rendering)은 웹 애플리케이션이 클라이언트 측에서 자바스크립트를 사용하여 동적으로 콘텐츠를 렌더링하는 방식이기 때문에 이렇게 디폴드로 적혀있다. 또한, 아래와 같이
Response에서 아직 아무 내용이 없는 빈 div로 정의되어 있거나, 주석을 해석하면, 브라우저에서 직접 이 파일을 열면 빈 페이지가 표시
한다고 되어있다.
반면, 서버사이드로 만든, 웹의 경우에는 localhost의 response부분에 body에 태그 내용이 담겨져 있는 것을 확인 할 수 있다.
Nextjs를 통해 매번 ssr를 간편하게 사용했지만, 직접 바닐라 js로 구현해보며 동작과정을 한 번더 깊게 알 수 있었고 개념적으로 알고 있던 부분을 실제 테스트를 해보며 더 잘 파악할 수 있었습니다.