๋ผ์ฐํ ์ ์์์ผ ํ๋ ์ด์
์๋ก๊ณ ์นจ ์์ด ์ฌ์ฉ์์๊ฒ ๋ ๋์ ํ์ดํผ๋งํฌ ๊ฒฝํ์ ์ ๊ณตํ๊ณ ์ถ๋ค๋ฉด
์ด ๋ฌธ์๋ฅผ ๋ณด๊ณ ๋๋ฉด
๋ผ์ฐํ
์ ์ํ ์ ํต์ ์ธ ๋งํฌ, AJAX, PJAX ๋ฐฉ์์ ์ดํดํ ์ ์๋ค.
Browser History API๋ฅผ ์ด์ฉํ์ฌ ์๋ก ๊ณ ์นจ ์์ด ํ์ด์ง๋ฅผ ๋ ๋๋งํ ์ ์๋ค.
๋ผ์ฐํ
๋ผ์ฐํ
์ด๋ ์ถ๋ฐ์ง์์ ๋ชฉ์ ์ง๊น์ง์ ๊ฒฝ๋ก๋ฅผ ๊ฒฐ์ ํ๋ ๊ธฐ๋ฅ์
๋๋ค. ์ ํ๋ฆฌ์ผ์ด์
์ ๋ผ์ฐํ
์ ์ฌ์ฉ์๊ฐ ํ์คํฌ๋ฅผ ์ํํ๊ธฐ ์ํด ์ด๋ค ํ๋ฉด(view)์์ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ ํํ๋ ๋ด๋น๊ฒ์ด์
์ ๊ด๋ฆฌํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ ์๋ฏธํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์๊ฐ ์์ฒญํ URL ๋๋ ์ด๋ฒคํธ๋ฅผ ํด์ํ๊ณ ์๋ก์ด ํ์ด์ง๋ก ์ ํํ๊ธฐ ์ํ ๋ฐ์ดํฐ๋ฅผ ์ทจ๋ํ๊ธฐ ์ํด ์๋ฒ์ ํ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ณ ํ๋ฉด์ ์ ํํ๋ ์ํ ์ผ๋ จ์ ํ์๋ฅผ ์๋ฏธํฉ๋๋ค.
๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ฉด์ ์ ํํ๋ ๊ฒฝ์ฐ๋ ์๋์ ๊ฐ์ต๋๋ค.
1.
๋ธ๋ผ์ฐ์ ์ ์ฃผ์์ฐฝ์ URL์ ์
๋ ฅํ๋ฉด ํด๋น ํ์ด์ง๋ก ์ด๋ํ๋ค.
2.
์นํ์ด์ง์ ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด ํด๋น ํ์ด์ง๋ก ์ด๋ํ๋ค.
3.
๋ธ๋ผ์ฐ์ ์ ๋ค๋ก ๊ฐ๊ธฐ ๋๋ ์์ผ๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์ฌ์ฉ์๊ฐ ๋ฐฉ๋ฌธํ ์นํ์ด์ง์ ๊ธฐ๋ก(history)์ ๋ค ๋๋ ์์ผ๋ก ์ด๋ํ๋ค.
์ด ๋ผ์ฐํ
์ ์ฒ๋ฆฌํ๋ ๋ฐ๋ ์ฃผ๋ก 3๊ฐ์ง ๋ฐฉ์์ด ์์ต๋๋ค.
1. ์ ํต์ ์ธ ๋งํฌ ๋ฐฉ์
์ ํต์ ๋งํฌ ๋ฐฉ์์ link tag๋ก ๋์ํ๋ ๊ธฐ๋ณธ์ ์ธ ์นํ์ด์ง์ ๋์ ๋ฐฉ์์
๋๋ค.
link tag(<a href="about.html">About</a> ๋ฑ)์ ํด๋ฆญํ๋ฉด href์ ๊ฐ์ธ ๋ฆฌ์์ค์ ๊ฒฝ๋ก๊ฐ URL์ path์ ์ถ๊ฐ๋์ด ์ฃผ์์ฐฝ์ ๋ํ๋๊ณ ํด๋น ๋ฆฌ์์ค๋ฅผ ์๋ฒ์ ์์ฒญํฉ๋๋ค. ์ด๋ ์๋ฒ๋ html๋ก ํ๋ฉด์ ํ์ํ๋๋ฐ ํ์ํ ์์ ํ ๋ฆฌ์์ค๋ฅผ ํด๋ผ์ด์ธํธ์ ์๋ตํฉ๋๋ค. ์ด๋ฅผ ์๋ฒ ๋ ๋๋ง์ด๋ผ ํฉ๋๋ค. ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ๊ฐ ์๋ตํ html์ ์์ ํ๊ณ ๋ ๋๋งํฉ๋๋ค. ์ด๋ ์ด์ ํ์ด์ง์์ ์์ ๋ html๋ก ์ ํํ๋ ๊ณผ์ ์์ ์ ์ฒด ํ์ด์ง๋ฅผ ๋ค์ ๋ ๋๋งํ๊ฒ ๋๋ฏ๋ก ์๋ก ๊ณ ์นจ์ด ๋ฐ์ํฉ๋๋ค.
์ด ๋ฐฉ์์ JavaScript๊ฐ ํ์ ์์ด ์๋ต๋ html๋ง์ผ๋ก ๋ ๋๋ง์ด ๊ฐ๋ฅํ๋ฉฐ ํ์ด์ง๋ง๋ค ๊ณ ์ ์ URL์ด ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์ history ๊ด๋ฆฌ ๋ฐ SEO ๋์์ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ํ์ง๋ง ์ค๋ณต๋ ๋ฆฌ์์ค๋ฅผ ์์ฒญํ ๋ ๋ง๋ค ์์ ํด์ผ ํ๋ฉฐ, ์ ์ฒด ํ์ด์ง๋ฅผ ๋ค์ ๋ ๋๋งํ๋ ๊ณผ์ ์์ ์๋ก ๊ณ ์นจ์ด ๋ฐ์ํ์ฌ ์ฌ์ฉ์ฑ์ด ์ข์ง ์์ ๋จ์ ์ด ์์ต๋๋ค.
2. AJAX๋ฅผ ํตํ ๋ฐฉ์
์ ํต์ ๋งํฌ ๋ฐฉ์์ ํ์ฌ ํ์ด์ง์์ ์์ ๋ html๋ก ํ๋ฉด์ ์ ํํ๋ ๊ณผ์ ์์ ์ ์ฒด ํ์ด์ง๋ฅผ ์๋ก ๋ ๋๋งํ๊ฒ ๋๋ฏ๋ก ์๋ก ๊ณ ์นจ์ด ๋ฐ์ํฉ๋๋ค. ๊ฐ๋จํ ์นํ์ด์ง๋ผ๋ฉด ๋ฌธ์ ๋ ๊ฒ์ด ์๊ฒ ์ง๋ง ๋ณต์กํ ์นํ์ด์ง์ ๊ฒฝ์ฐ, ์์ฒญ๋ง๋ค ์ค๋ณต๋ HTML๊ณผ CSS, JavaScript๋ฅผ ๋งค๋ฒ ๋ค์ด๋ก๋ ํด์ผํ๋ฏ๋ก ์๋ ์ ํ์ ์์ธ์ด ๋ฉ๋๋ค.
์ด๋ฌํ ์ ํต์ ๋งํฌ ๋ฐฉ์์ ๋จ์ ์ ๋ณด์ํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด AJAX(Asynchronous JavaScript and XML)์
๋๋ค. AJAX๋ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํด์ ๋น๋๊ธฐ์ (Asynchronous)์ผ๋ก ์๋ฒ์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๊ตํํ ์ ์๋ ํต์ ๋ฐฉ์์ ์๋ฏธํฉ๋๋ค.
AJAX๋ฅผ ํตํ ๋ฐฉ์์ ์ด์ฉํ๋ฉด ์ต์ด์ ์๋ฒ๋ก๋ถํฐ ์นํ์ด์ง๊ฐ ๋ฐํ๋๋ฉด ์ดํ ๋ถํฐ๋ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ผ๋ฉฐ ํ๋ฉด์ ๊ฐฑ์ ํฉ๋๋ค.
์ ์์ ๋ฅผ ์ดํด๋ณด๋ฉด link tag(<a id="home">Home</a> ๋ฑ)์ href ์์ฑ์ ์ฌ์ฉํ์ง ์์ต๋๋ค. ๋ด๋น๊ฒ์ด์
์ด ํด๋ฆญ๋๋ฉด link tag์ ๊ธฐ๋ณธ ๋์์ preventํ๊ณ AJAX์ ์ฌ์ฉํ์ฌ ์๋ฒ์ ํ์ํ ๋ฆฌ์์ค๋ฅผ ์์ฒญํํ ์๋ต๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ์ฌ html์ ์์ฑํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ถํ์ํ ๋ฆฌ์์ค ์ค๋ณต ์์ฒญ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ๋ํ ํ์ด์ง ์ ์ฒด๋ฅผ ์๋ก ๋ ๋๋งํ ํ์๊ฐ ์๊ณ ๊ฐฑ์ ์ด ํ์ํ ์ผ๋ถ๋ง ๋ก๋ํ๋ฉด ๋๋ฏ๋ก ๋น ๋ฅธ ํผํฌ๋จผ์ค์ ๋ถ๋๋ฌ์ด ํ๋ฉด ํ์ ํจ๊ณผ๋ฅผ ๊ธฐ๋ํ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ด ๋ฐฉ์์ URL์ ๋ณ๊ฒฝ์ํค์ง ์์ผ๋ฏ๋ก ํด๋น ํ์ด์ง์ ์ฃผ์๊ฐ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค. ์ด๋ ๋ธ๋ผ์ฐ์ ์ ๋ค๋ก๊ฐ๊ธฐ, ์์ผ๋ก๊ฐ๊ธฐ ๋ฑ์ history ๊ด๋ฆฌ๊ฐ ๋์ํ์ง ์์์ ์๋ฏธํฉ๋๋ค. ๋ํ ์ฃผ์๊ฐ ๋ณ๊ฒฝ๋์ง ์๊ธฐ์ ์๋ก๊ณ ์นจ์ ํด๋ฆญํ๋ฉด ์ธ์ ๋ ์ฒซํ์ด์ง๊ฐ ๋ค์ ๋ก๋ฉ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ด ๋ํ SEO์ ์์ด์๋ ์ทจ์ฝํ ๊ตฌ์กฐ์
๋๋ค.
3. Hash ๋ฐฉ์
AJAX ๋ฐฉ์์ ๋ถํ์ํ ๋ฆฌ์์ค ์ค๋ณต ์์ฒญ์ ๋ฐฉ์งํ ์ ์๊ณ , ์๋ก๊ณ ์นจ์ด ์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ตฌํํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ง๋ง history ๊ด๋ฆฌ๊ฐ ๋์ง ์๋ ๋จ์ ์ด ์์ต๋๋ค. ์ด๋ฅผ ๋ณด์ํ ๋ฐฉ๋ฒ์ด Hash๋ฐฉ์์
๋๋ค.
Hash ๋ฐฉ์์ URI์ fragment identifier(#service)์ ๊ณ ์ ๊ธฐ๋ฅ์ธ ์ต์ปค(anchor)๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ ์์ ๋ฅผ ์ดํด๋ณด๋ฉด link tag(<a href="#about">About</a> ๋ฑ)์ href ์์ฑ์ hash๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ฆ, ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด hash๊ฐ ์ถ๊ฐ๋ URI๊ฐ ์ฃผ์์ฐฝ์ ํ์๋ฉ๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด ๋ URL์ด ๋์ผํ ์ํ์์ hash๊ฐ ๋ณ๊ฒฝ๋๋ ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ์ ์นํ์ด์ง๋ฅผ ์์ฒญํ์ง ์์ต๋๋ค. ์๋ํ๋ฉด hash๋ ์์ฒญ์ ์ํ ๊ฒ์ด ์๋๋ผ ์ต์ปค(anchor)๋ก ์นํ์ด์ง ๋ด๋ถ์์ ์ด๋์ ์ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ทธ๋์ hash ๋ฐฉ์์ ์๋ฒ์ ์๋ก์ด ์์ฒญ์ ๋ณด๋ด์ง ์๊ณ , ๊ฐฑ์ ๋์ง ์์ง๋ง ํ์ด์ง๋ง๋ค ๊ณ ์ ์ ๋
ผ๋ฆฌ์ URL์ด ์กด์ฌํ๋ฏ๋ก history๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
const root = document.querySelector(".container");
const routes = {
"": "/data/home.json",
service: "/data/service.json",
about: "/data/about.html",
};
const render = async () => {
const hash = location.hash.replace("#", "");
const url = routes[hash];
const res = await fetch(url);
const { title, content } = await res.json();
root.innerHTML = `<h1>${title}</h1><p>${content}</p>`;
};
window.addEventListener("hashchange", render);
window.addEventListener("DOMContentLoaded", render);
JavaScript
๋ณต์ฌ
uri์ hash๊ฐ ๋ณ๊ฒฝํ๋ฉด ๋ฐ์ํ๋ ์ด๋ฒคํธ์ธ hashchange์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ์ฌ hash์ ๋ณ๊ฒฝ์ ๊ฐ์งํ์ฌ ํ์ํ ์ถ๊ฐ์ ์ธ Ajax์์ฒญ์ ํ ์ ์์ต๋๋ค.
์ด hash ๋ฐฉ์์ ๋จ์ ์ uri์ ๋ถํ์ํ #์ด ๋ค์ด๊ฐ๋ค๋ ๊ฒ์
๋๋ค. ์ผ๋ฐ์ ์ผ๋ก hash ๋ฐฉ์์ ์ฌ์ฉํ ๋ #!์ ์ฌ์ฉํ๊ธฐ๋ ํ๋๋ฐ ์ด๋ฅผ ํด์๋ฑ
(Hash-bang)์ด๋ผ๊ณ ๋ถ๋ฆ
๋๋ค. ๋ ๋ค๋ฅธ ๋ฌธ์ ๋ SEO ์ด์์
๋๋ค. ๊ฒ์์์ง์ ์น์ฌ์ดํธ์ ์ฝํ
์ธ ๋ฅผ ์์งํ๊ธฐ ์ํด ๋ณดํต JavaScript๋ฅผ ์คํ์ํค์ง ์๊ธฐ ๋๋ฌธ์ hash ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด์ง ์ฌ์ดํธ์ ์ฝํ
์ธ ๋ฅผ ์์งํ๊ธฐ ์ด๋ ต์ต๋๋ค. ๊ตฌ๊ธ์ ํด์๋ฑ
์ ์ผ๋ฐ URL์ ๋ณ๊ฒฝ์์ผ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ง๋ง, ๋ค๋ฅธ ๊ฒ์ ์์ง์ hash ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด์ง ์ฌ์ดํธ์ ์ฝํ
์ธ ๋ฅผ ์์งํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
History Api๋ฅผ ์ด์ฉํ PJAX
์์์ ์ดํด๋ณธ ๊ฒ์ฒ๋ผ hash ๋ฐฉ์์ ๊ฐ์ฅ ํฐ ๋จ์ ์ SEO ์ด์์
๋๋ค. ์ด๋ฅผ ๋ณด์ํ ๋ฐฉ๋ฒ์ด HTML5์ Histroy API์ธ pushState์ popstate ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ PJAX ๋ฐฉ์์
๋๋ค.
์ ์์ ๋ฅผ ์ดํด๋ณด๋ฉด link tag(<a href="/service">Service</a> ๋ฑ)์ href ์์ฑ์ path๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๋ด๋น๊ฒ์ด์
์ด ํด๋ฆญ๋๋ฉด ํด๋น ๋งํฌ์ path๊ฐ ์ถ๊ฐ๋ URI๊ฐ ์๋ฒ๋ก ์์ฒญ๋ฉ๋๋ค. PJAX ๋ฐฉ์์ ์ด ๊ณผ์ ์์ ๋ด๋น๊ฒ์ด์
ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ์บ์นํ๊ณ preventDefault๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ์์ฒญ์ ๋ฐฉ์งํฉ๋๋ค. ์ดํ, href ์ดํธ๋ฆฌ๋ทฐํธ์ path์ ์ฌ์ฉํ์ฌ AJAX ์์ฒญ์ ํฉ๋๋ค.
์ด๋ AJAX ์์ฒญ ์์ฒด๋ ์ฃผ์์ฐฝ์ URL์ ๋ณ๊ฒฝ์ํค์ง ์์ history ๊ด๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ ์ฌ์ฉํ๋ ๊ฒ์ด pushState ๋ฉ์๋์
๋๋ค. pushState ๋ฉ์๋๋ ์ฃผ์์ฐฝ์ URL์ ๋ณ๊ฒฝํ๊ณ URL์ history entry๋ก ์ถ๊ฐํฉ๋๋ค.
const root = document.querySelector(".container");
const navigation = document.getElementById("navigation");
const routes = {
"/": "/data/home.json",
"/service": "/data/service.json",
"/about": "/data/about.html",
};
const render = async (path) => {
const url = routes[path];
const res = await fetch(url);
const { title, content } = await res.json();
root.innerHTML = `<h1>${title}</h1><p>${content}</p>`;
};
window.addEventListener("popstate", (e) => {
render(e.state.path);
});
navigation.addEventListener("click", (e) => {
if (!e.target.matches("#navigation > li > a")) return;
e.preventDefault();
const path = e.target.getAttribute("href");
history.pushState({ path }, null, path);
render(path);
});
// ์ต์ด ์ด๊ธฐํ ํ์ด์ง
render("/");
JavaScript
๋ณต์ฌ
PJAX ๋ฐฉ์์ ์๋ฒ์ ์๋ก์ด ์์ฒญ์ ๋ณด๋ด์ง ์์ผ๋ฉฐ ๋ฐ๋ผ์ ํ์ด์ง๊ฐ ์๋ก๊ณ ์นจ ๋์ง ์์ต๋๋ค. ํ์ง๋ง ํ์ด์ง๋ง๋ค ๊ณ ์ ์ URL์ด ์กด์ฌํ๋ฏ๋ก history๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ๋ค๋ง, ๋ธ๋ผ์ฐ์ ์ ์๋ก๊ณ ์นจ ๋ฒํผ์ ํด๋ฆญํ๋ฉด https://localhost:8080/about์ ๊ฐ์ ์์ฒญ์ด ์๋ฒ๋ก ์ ๋ฌ๋๋๋ฐ. ์ด๋ ์๋ฒ๋ URL์ ๋ฐ๋ผ ํด๋น ๋ฆฌ์์ค๋ฅผ HTML๋ก ํด๋ผ์ด์ธํธ์ ์๋ตํด์ฃผ์ด์ผ ์ต์ด ํ๋ฉด์ด ํ์๋ ์ ์์ต๋๋ค.
์ ๋ฆฌ
์ฌ๊ธฐ๊น์ง ์ ํต์ ๋งํฌ ๋ฐฉ์์์ PJAX ๋ฐฉ์๊น์ง SPA์ ๋ฐ์ ๊ณผ์ ์ผ๋ก ์ด์ด์ง๋ ๋ผ์ฐํ
์ ๋ํด ์์๋ดค์ต๋๋ค. SPA๋ ํ๋์ HTMLํ์ด์ง๋ก ์คํ๋๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์
๋๋ค. ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ๋ทฐ๋ก ์ด๋ํ ๋ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ทฐ๋ฅผ ๋์ ์ผ๋ก ๋ค์ ๊ทธ๋ ค ํ์ด์ง๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ฐ ์ ๊ทผ ๋ฐฉ์์ ๊ธฐ์กด์ ๋ค์ค ํ์ด์ง ์ ํ๋ฆฌ์ผ์ด์
์์ ํ์ด์ง๊ฐ ํ์ ์ ์ฌ์ฉ์๊ฐ ๊ฒฝํํ๋ ์ง์ฐ์ ์ ๊ฑฐํด ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. SPA ์ ํ๋ฆฌ์ผ์ด์
์ ์๋ฒ์์ ์ํธ์์ฉ์ ์ํด AJAX๋ฅผ ์ฌ์ฉํฉ๋๋ค. ํ์ง๋ง ๋ชจ๋ AJAX ์ ํ๋ฆฌ์ผ์ด์
์ด SPA์ผ ํ์๋ ์์ต๋๋ค. ์๋ ๊ทธ๋ฆผ์ ํ์ค ์น ์ ํ๋ฆฌ์ผ์ด์
๊ณผ, ๊ฐ๋จํ AJAX ์ ํ๋ฆฌ์ผ์ด์
, ๋จ์ผ ํ์ด์ง ์ ํ๋ฆฌ์ผ์ด์
๊ฐ์ ์ฐจ์ด๋ฅผ ๋ณด์ฌ์ค๋๋ค.
History API
ํ์คํ ๋ฆฌ API๋ฅผ ํตํด ๊ฐ๋ฐ์๋ ์ฌ์ฉ์ ํ์ ํ์คํ ๋ฆฌ๋ฅผ ์กฐ์ํ ์ ์์ต๋๋ค.
์ฐธ๊ณ ๋งํฌ
โข