Amada Coding Club

[Web Routing] Routing의 발전 과정에 대해 알아보자(ajax, hash, pjax) 본문

Front-End/관련 지식들

[Web Routing] Routing의 발전 과정에 대해 알아보자(ajax, hash, pjax)

아마다회장 2023. 1. 13. 12:37

어우 원래 이 내용을 쓰려고 했는데 어찌하다보니 SEO에 대한 이야기를 해버렸다

2023.01.12 - [Front-End/관련 지식들] - [Front-End] SEO의 개념과 중요성

 

우리가 무언가 개발을 할 때 라우팅을 많이 사용한다. React에서도 React-router-dom을 사용하고 다른 라이브러리나 프레임워크에서 라우팅을 지원한다.

 

이전에는 라우팅이라는 주제에 대해 모르는 상태에서 그냥 그렇구나하고 개발했었는데 이제는 라우팅에 대해 알고 있어야 할 것 같아서 라우팅에 대해 알아보고자 한다.

 

Routing이란?

Routing은 route(경로)에 ing가 붙여진 것으로 출발지(기존 주소)에서 목적지(가려고 하는 주소)까지의 경로를 설정하는 기능이다. 다시 말해 기존 화면에서 다른 화면으로 화면을 전환할 때 그 주소나 방향(네비게이션)을 관리하는 기능을 의미한다. 

네비게이션을 관리한다는 의미는 사용자가 요청한 URL이나 이벤트를 해석하고 새로운 페이지로 전환하기 위해 필요한 데이터를 서버에 요청하고 받은 데이터를 이용해 페이지를 전환하는 일련의 과정을 의미한다.

 

브라우저가 화면을 전환하는 과정에는

  • 브라우저 주소창에 URL을 입력하는 경우
  • HTML의 링크(a=anchor 태그)를 클릭하는 경우
  • 뒤로 가기나 앞으로 가기 버튼을 클릭해 방문 기록의 앞 뒤로 이동하는 경우

URL을 통해 페이지를 이동하기 위해선, 각 페이지마다 구별할 수 있는 유일한 URL주소 값을 가지고 있어야 한다.

 

이 라우팅이 어떻게 발전해왔는지 살펴보자

 

우선 전통적 방식이다.

전통적 링크 방식

결론을 먼저 말하면 전통적인 방식은 링크를 이용해 서버에 html을 요청해 데이터를 받고 화면을 렌더링하는 서버 사이드 렌더링 을 사용한다. 

 

처음부터 살펴보자.

전통적 링크 방식은 link 태그(a 태그)로 동작하는 기본적인 웹페이지 동작 방식을 나타낸다

<a href="/amadaclub.html">amada</a>

만약에 이 아마다 링크 태그가 있다고 가정할 때 이 태그를 클릭하게 되면 href의 값(리소스 경로)가 URL path에 추가되어 해당 주소창에 나타나고 그 리소스를 서버에 요청한다. 이후 서버에서 작업을 거쳐 완전한 리소스를 클라이언트에게 제공해주는데 이를 서버 사이드 렌더링이라고 한다.

 

이 방식은 클라이언트 측에서 응답받은 html만으로도 렌더링이 가능하고 각 페이지마다 고유의 URL이 있어 히스토리나 SEO 관리에 용이하다는 장점이 있지만 새로운 리소스가 기존 리소스와 중복되더라도 완전히 통째로 가지고 와야 한다는 점에서 대기 시간이 길어서 사용자 경험이 좋지 못하다는 단점이 있다. 이 단점을 보완하기위해 나온 방식이 ajax이다

 

AJAX(Asynchronous Javascript And Xml)

Ajax는 이름 그대로 자바스크립트를 이용해 비동기적으로 서버와 브라우저(클라이언트)가 데이터를 교환할 수 있는 통신 방식을 의미한다. 

이름에는 Xml을 사용한다고 나와있지만 json과 같은 다양한 데이터 형식을 사용할 수 있다.

<a id="navi" href="/amadaclub">amada</a>
<div id="root">루트</div>

위와 같은 link 태그와 div 태그가 있다고 가정할 때

link 태그를 누르게 되면 자바스크립트는 preventDefault()를 실행해 자동으로 서버에 데이터를 요청하는 작업을 멈춘다(새로고침도 이루어지지 않게 됨) 그리고 href의 path를 이용해서 ajax요청을 하게 된다. 

 

즉, a태그를 누르게 되면 비동기적으로(ajax를 요청하는 함수를 실행할 때 다른 작업이 멈추는게 아니라 다른 작업들은 계속 진행됨) 태그의 path에 해당하는 정보를 요청하고 그 정보를 가지고 와서 #root를 업데이트 한다.(새로고침 없이!)

어랏 이거 SPA 아닌감. -> AJAX의 개발로 SPA와 CSR이 가능해졌고 발전했다.

다음은 위 태그에 대한 ajax요청의 간단한 예시다

// index.js

const $root = document.getElementById('root');
const $navigation = document.getElementById('navi');

const routes = [
  { path: '/', component: Home },
  { path: '/amadaclub', component: Amada },
];

const render = async path => {
  try {
    const component = routes.find(route => route.path === path)?.component || NotFound;
    $root.replaceChildren(await component());
  } catch (err) {
    console.error(err);
  }
};

// TODO: ajax 요청은 주소창의 url을 변경시키지 않으므로 history 관리가 되지 않는다.
$navigation.onclick = e => {
  e.preventDefault();
  const path = e.target.getAttribute('href');
  render(path);
};

// TODO: 주소창의 url이 변경되지 않기 때문에 새로고침 시 현재 렌더링된 페이지가 아닌 루트 페이지가 요청된다.
window.addEventListener('DOMContentLoaded', () => render('/'));


const createElement = domString => {
  const $temp = document.createElement('template');
  $temp.innerHTML = domString;
  return $temp.content;
};

const fetchData = async url => {
  const res = await fetch(url);
  const json = await res.json();
  return json;
};

export const Home = async () => {
  const { title, content } = await fetchData('/api/home');
  return createElement(`<h1>${title}</h1><p>${content}</p>`);
};

export const Amada = async () => {
  const { title, content } = await fetchData('/api/amada');
  return createElement(`<h1>${title}</h1><p>${content}</p>`);
};

export const NotFound = () => createElement('<h1>404 NotFound</p>');

새로고침 없이 화면을 전환하고 필요한 부분만 변경시킨다는 점에서 사용자 경험을 좋게 할 수 있지만 몇 가지 단점이 존재한다.  ajax는 주소창의 URL을 변경시키지 않기 때문에 모든 페이지가 하나의 URL로 작동되어 history 관리가 되지 않는다. 그래서 이전, 다음 페이지로 돌아가는 함수(history.back, history.go 등)이 작동하지 않는다. 또한 새로고침을 하게 되면 같은 URL을 가지기 때문에 제일 첫 화면이 나오게 된다. 그리고 동일한 URL로 인해 SEO 문제에서도 자유롭지 않다.

 

이 중 history 관리 문제를 해결하기 위해 나온 방법이 Hash 방식이다.

Hash

아래 내용을 보기 전에 URL과 URI의 차이점을 보고 가자

2023.01.13 - [Front-End/관련 지식들] - [Web]URL과 URI의 차이

 

[Web]URL과 URI의 차이

보통 웹 주소에 대해 말할 때 URL이라는 말을 많이 사용한다. 그런데 이번에 Router에 대한 글을 쓰면서 URL이외에 URI라는 말을 많이 썼다. URI는 뭐고 URL은 뭔지 궁금해서 정리를 해보고자 한다. 먼

amadaclub.tistory.com

 

hash는 URI의 fragment identifier(#~~~ 이거 hash, hash mark라고 부름)의 고유 기능인 anchor를 사용한다.

<a href="#amada">amada</a>

 다음과 같이 href에 해시를 사용하고 있다. 이 때 a를 클릭하게 되면 hash가 추가된 URI가 주소창에 표시된다.

클릭 전
클릭 후

그러나 URL이 동일한 상태에서 hash만 변경된다면 브라우저는 서버에 아무런 요청도 하지 않는다. 다시 말해 URL의 hash는 변경되어도 페이지가 갱신되지는 않는다.

hash값이 추가되도 페이지가 갱신되지 않아 input 값이 유지됨

그러나 페이지는 갱신되지 않아도 페이지마다 고유의 논리적 URL이 존재하기 때문에 history관리에는 문제가 없다.

hash 방식은 url의 hash가 변경되면 이를 감지하는 hashchange 이벤트를 사용해서 필요한 ajax요청을 수행한다.

hash 방식의 js 코드는 아래와 같다.

import { Home, Amada, NotFound } from './components.js';

const $root = document.getElementById('root');

const routes = [
  { path: '', component: Home },
  { path: 'amada', component: Amada },
  { path: 'about', component: About },
];

const render = async () => {
  try {
    // url의 hash를 취득
    const hash = window.location.hash.replace('#', '');
    const component = routes.find(route => route.path === hash)?.component || NotFound;
    $root.replaceChildren(await component());
  } catch (err) {
    console.error(err);
  }
};

// 네비게이션을 클릭하면 url의 hash가 변경되기 때문에 history 관리가 가능하다.
// 단, url의 hash만 변경되면 서버로 요청은 수행하지 않는다.
// url의 hash가 변경하면 발생하는 이벤트인 hashchange 이벤트를 사용하여 hash의 변경을 감지하여 필요한 ajax 요청을 수행한다.
// hash 방식의 단점은 url에 /#foo와 같은 해시뱅(HashBang)이 들어간다는 것이다.
window.addEventListener('hashchange', render);

// 새로고침을 하면 DOMContentLoaded 이벤트가 발생하고
// render 함수는 url의 hash를 취득해 새로고침 직전에 렌더링되었던 페이지를 다시 렌더링한다.
window.addEventListener('DOMContentLoaded', render);

 

hash의 단점으로는 불필요한 #이 사용된다는 점과(#!로 쓰이는 해시뱅으로 대체할 수 있음) 자바스크립트를 실행하지 못하는 웹 크롤러에선 hash 방식으로 만들어진 사이트를 제대로 크롤링하지 못한다는 단점(SEO)이 있다. 

 

이 SEO 문제를 해결하기위해 나온 기술이 PJAX이다

 

PJAX(pushstate + AJAX)

기존의 AJAX는 새로고침 없이 필요한 부분의 데이터를 js를 통해 가지고와 업데이트한다고 했다. 그러나 URL을 변경시키지 않기 때문에 모든 페이지가 같은 URL을 가지고 있어 Histroy 관리에 문제가 발생한다고 했다.

이 ajax에 pushstate 기능을 추가해 각 페이지가 URL을 갖게 하는 방법이 PJAX 방법이다.

<a id="navi" href="/amadaclub">amada</a>
<div id="root">루트</div>

 위 태그가 있다고 생각해보자 우선 a태그를 클릭하게 되면 preventDefault를 실행해 서버로의 요청을 막는다. 그리고 pushstate를 실행해 서버 요청은 하지 않고 url의 주소값을 변경한다. 그리고 기존 ajax요청을 하면서 필요한 데이터를 가지고 오게 된다. 

 

그러면 각 페이지마다 고유한 URL을 가지고 있기 때문에 history 관리가 가능하고 hash를 사용하지 않기 때문에 SEO에도 문제가 발생하지 않는다. 

 

그러나 pjax는 새로고침을 할 경우 hash(URL에 fragment identifier만 추가된 형태)나 ajax(모든 페이지가 동일한 URL)와 달리 해당 URL에 대한 서버 요청이 필요하기 때문에 서버의 지원이 필요하게 된다. 즉, pjax는 서버 사이드 렌더링과 AJAX(클라이언트 사이드 렌더링)이 같이 있는 형태라고 보면 된다. 

 

이렇게 라우팅에 대한 내용을 써봤다. 어떻게 보니 이 게시글을 쓰려다가 4개의 게시글을 써버렸다. 그래도 글을 쓰면서 생각보다 많은 내용들을 배울 수 있어 좋았다. 후회하지는 않는다!

 

참조

https://poiemaweb.com/js-spa 

https://heecheolman.tistory.com/41

https://hanamon.kr/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-ajax%EB%9E%80-spa%EB%A5%BC-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B8%B0%EC%88%A0/