MDN의 “Using HTTP Cookies”를 기반으로 작성되었습니다.
HTTP에서 cookie를 다루지 못 했다.
그래서 지금이라도 어떤 것인지, 어떻게 만들어 졌는지, 지금은 어떻게 쓰이는 지 알아본다.
HTTP통신은 무상태성(stateless)을 띈다.
무상태성이란 “서버가 클라이언트의 이전 요청 상태를 기억하지 못 한다”을 뜻한다.
그러면 왜 서버는 무상태성을 택했을까? 찾을 수는 없었지만 GPT와 대화를 통해서 무상태성을 가지게 되면서 다음 3가지 장점을 얻었다.
그래서 클라이언트의 상태를 전달받아야 했고 이를 보완하기 위해 cookie라는 저장소가 개발되었고 처음 용도는 사용자가 Netscape 페이지 “방문 여부 확인”을 체크하는 거였다.
Cookie는 아래와 같이 설정을 포함해서 전달된다.
Set-Cookie: session_id=abc123; HttpOnly; Secure; Path=/
// cookie의 값은 session_id라는 key와 abc123이라는 value
// HttpOnly 및 Secure라는 보안 옵션
// path는 다음 위치에서 cookie 전송을 의미. 현재는 root 하위 모든 곳에서 전달
// 만약 /dashboard 하위에서만 cookie를 전송하고 싶으면 Path=/dashboard 설정
그리고 아래와 같은 기본적인 특징을 가지고 있다.
HttpOnly 옵션을 사용하면 cookie는 JavaScript로 접근할 수 없지만, 요청마다 자동으로 포함된다.
반대로 설정이 되어 있지 않다면 다음과 같은 코드로 cookie의 정보를 확인할 수 있다.
console.log(document.cookie)
// '_ga_tag=tag값; preferredlocale=ko;'
Secure 옵션은 오직 HTTPS 요청에만 전송될 수 있게 해주는 옵션이다. 그러나 HttpOnly 속성이 없다면 JavaScript로 여전히 접근이 가능하다
SameSite 옵션은 cross-site 요청과 함께 전송되지 않게 만들어줘서 CSRF에 어느정도 방어를 할 수 있게 해준다.
SameSite 옵션은 총 3가지(Strict, Lax, None)로 나누어진다.
| 속성 | Strict | Lax(미설정 시 Chrome의 기본값) | None |
|---|---|---|---|
| 크로스 사이트 요청 | ❌ 쿠키 전송 안됨 | ✅ 링크 클릭 시 쿠키 전송됨 | ✅ 모두 허용 |
| POST 요청에서 쿠키 전송 | ❌ 불가능 | ❌ 불가능 | ✅ 모두 허용 |
| GET 요청에서 쿠키 전송 | ❌ 불가능 | ✅ 링크 클릭 후 이동 시 가능 | ✅ 모두 허용 |
| 비고 | 로그인 유지, 민감한 세션 데이터 | 파트너 링크 추적, 기본적인 보안 | None으로 설정된 쿠기는 보안을 위해서 Secure 지정 필수 |
| 속성 | 설명 | 예제 |
|---|---|---|
Domain | 쿠키를 적용할 도메인을 지정 | Domain=example.com이면 example.com과 sub.example.com에서 유효 |
Path | 쿠키를 적용할 URL 경로를 지정 | Path=/docs이면 /docs/ 이하의 경로에서만 유효 |
먼저 서버에서 클라이언트로 HTTP header에 cookie 정보를 담아서 클라이언트로 전달한다
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure
이후 클라이언트의 모든 요청에는 cookie정보가 담긴 채로 서버로 전달된다
GET /dashboard HTTP/1.1
Host: example.com
Cookie: session_id=abc123
많은 블로그글들의 제목을 통해서 눈에는 익었지만 정확히 어떤 것인 지 몰라서 이번 기회에 정리해본다.
네이티브 앱은 “소장하는 경험”을 주면서 아래 장점을 제공한다.
웹사이트는 브라우저를 통해서 “방문하는 장소”라는 경험을 주면서 아래 장점을 제공한다.
MDN을 인용하자면 “웹 플렛폼 기술로 만들어졌지만 네이티브 앱(platform-specific apps) 경험을 제공하는 앱”이라고 한다. 즉, PWA는 설치 가능하면서도, 네이티브 앱처럼 강력한 기능을 갖춘 웹 애플리케이션이다.
두 개의 platform을 활용하기에 다양한 장점이 있지만 가장 큰 3가지 핵심요소(web.dev의 The three pillars of PWA design)는 아래와 같다.
| 핵심 요소 | 설명 | 예제 |
|---|---|---|
| Capable (유능함) | 네이티브 앱과 유사한 기능 제공(계속 기능 추가 중) | WebRTC(화상통화), 푸시 알림, WebAssembly |
| Reliable (안정성) | 빠른 로드와 오프라인에서도 실행 가능 | 서비스 워커를 이용한 캐싱 데이터 활용 |
| Installable (설치 가능) | 홈 화면에 앱 추가 가능 | OS의 앱 전환기 및 단축키 사용 가능 |
PWA는 기본적으로 웹사이트이다. 웹사이트를 구성하는 모든 것이 필요하며 그 외 추가적인 것을 알아본다.
웹 페이지이기에 HTML, CSS, JS는 필수이다.
PWA를 다운받을 수 있게 해주고 앱과 같은 프레임을 가질 수 있게 해주는 필수적인 요소이다.
그리고 manifest에는 브라우저가 설치가 가능할 수 있게 충분한 정보를 제공해줘야 한다.
index.html 상위 내에 manifest의 경로를 기재를 해주며, 멀티 페이지 앱이라면 모든 html 내부에 기재를 해줘야 한다.
<!doctype html>
<html lang="en">
<head>
<link rel="manifest" href="manifest.json" />
<!-- ... -->
</head>
<body></body>
</html>
Manifest는 JSON format으로 작성이 되며 형태는 아래와 같으며 이 중에 필수 적인 것은 아래와 같다.
{
"short_name": "MDN", // 앱 이름 표기 공간이 적을 시 표출될 이름
"name": "MDN Web Docs", // 설치될 앱의 이름
"icons": [
// 앱 아이콘으로 192px 및 512px 두 개의 사진 필수
{
"src": "/favicon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": ".", // 엡 시동 시 처음 시작할 url을 지정
"display": "standalone", // 앱 시동 시 디스플레이 설정. 현재는 앱과 같은 별개의 창인 standalone
"theme_color": "#000000",
"background_color": "#ffffff"
}
| 속성 | 설명 | 필수 여부 |
|---|---|---|
name | 앱의 전체 이름 | ✅ 필수 |
short_name | 축약된 앱 이름 (UI 공간 부족 시 사용) | ✅ 필수 |
icons | 앱 아이콘 (최소 192x192, 512x512 필요) | ✅ 필수 |
start_url | 앱 실행 시 시작할 URL | ✅ 필수 |
display | 실행 방식 (standalone, fullscreen, etc.) | ✅ 필수 |
theme_color | UI의 기본 색상 | ⛔ 선택 |
background_color | 초기 로딩 화면 배경색 | ⛔ 선택 |
정보가 전부 적혀 있다면 아래와 같은 설치 아이콘이 브라우저 검색창(URL) 노출이 된다.

PWA가 설치가능하려면 필요한 요소 중 하나다. 그리고 MDN에 다음과 같은 문장을 발견했다.
“file:// URL에서 로드된 리소스를 안전한 것으로 간주하는 보안 컨텍스트보다 더 엄격한 요구 사항”
”This is a more stringent requirement than secure context, which considers resources loaded from file:// URLs to be secure”
MDN - “Making PWAs installable”
이 부에 대해서는 자세한 내역이나 검색 항목이 없기에 GPT의 도움을 받았다.
1️⃣ Secure Context(보안 컨텍스트)란?
웹 브라우저가 “이 사이트는 보안이 잘 유지되고 있다”고 판단하는 기준이야.
보안 컨텍스트에서는 일부 보안이 중요한 웹 API를 사용할 수 있어.
예를 들어, 웹캠, 마이크, 서비스 워커, 푸시 알림 같은 기능은 보안 컨텍스트에서만 작동해.
“보안 컨텍스트(Secure Context)“의 기본 조건
🔹 HTTPS에서 실행된 웹사이트 → ✅ 안전
🔹 localhost에서 실행된 웹사이트 → ✅ 안전
🔹 file://에서 실행된 로컬 파일 → ✅ 안전 (일반적으로)
📌 즉, 일반적인 보안 기준에서는 file://도 보안 컨텍스트로 인정됨.
2️⃣ file:// URL은 뭐야?
file://은 인터넷을 통해 접속하는 게 아니라, 내 컴퓨터에 있는 파일을 직접 여는 방식이야.
예를 들어, 컴퓨터에 저장된 HTML 파일을 직접 열면 브라우저 주소창에 file://C:/Users/.../index.html 같은 주소가 나옴.
이 방식은 보안 위협이 적다고 간주되기 때문에, 일반적인 “보안 컨텍스트”에서는 허용됨.
📌 즉, 보통 웹에서는 file://도 안전한 환경으로 취급함.
3️⃣ 그런데, PWA는 왜 file://을 안전하지 않다고 보는 거야?
PWA는 설치 가능한 웹앱이기 때문에, 일반 웹사이트보다 더 높은 보안 기준을 요구해.
🔹 “보안 컨텍스트”보다 PWA 보안 기준이 더 엄격한 이유
• PWA는 일반 웹사이트가 아니라, 앱처럼 기기에 설치되는 형태이기 때문.
→ 즉, “네이티브 앱처럼 실행되는 프로그램”이라 더 엄격한 보안 기준이 필요해.
• file://에서 실행되면, 원격 서버가 아니라 내 PC의 파일을 실행하는 거라 신뢰할 수 없는 소스일 가능성이 있음.
→ 예를 들어, file:// 환경에서는 누군가가 내 PC에 악성 파일을 심어놓고 실행할 수도 있음.
• 만약 file://에서 PWA가 설치 가능하다면, 악성 코드가 포함된 PWA가 쉽게 실행될 수 있음.
📌 즉, PWA는 “보안 컨텍스트”보다 더 강한 보안 기준을 요구하기 때문에 file://은 안전하지 않다고 판단함.
요약하자면, PWA는 더 강한 보안 기준을 요구하기에 평소 안전하다고 생각하는 local 파일의 실행도 제한한다는 거다.
그러면 PWA에서 보안이 왜 더 중요하게 보는 것일까?
다운로드는 무언가를 받고 사용자 환경에 설치되는 프로그램들이다. 이 프로그램들이 사용자의 정보에 접근하고 공격할 수 있다. 기존 웹과는 다르게 PWA가 앱의 특성도 가지고 있기에 더 엄격한 보안 기준을 가진다고 볼 수 있다.

서비스 워커는 웹 워커 중 하나로써 웹 페이지와 네트워크 중간에서 proxy와 같은 역할을 해준다.
이들은 중간에 네트워커 요청을 중간에 가로채고 캐시에 접근 후 요청을 어떻게 처리할 지 판단한다. 또한, 브라우저가 오픈되어 있지 않아도 백그라운드에서 돌아가기에 PWA를 조금 더 네이티브 앱과 같은 경험을 가질 수 있게 해준다. 더 다양한 기능은 web.dev의 “Capabilities”를 참조하면 된다.
서비스워커는 다음과 같은 특징이 있다.
FetchEvent를 통해 범위 내부의 모든 네트워크 요청을 가로채기 가능또한, 서비스워커를 통하여 어떻게 캐싱을 하는지(web.dev의 “Caching”), 요청값을 캐싱에서 불러올 지 네트워크로 연결(web.dev의 “Serving”)할 지는 각각 링크에서 더 확인할 수 있다.
이런 강력한 기능을 제공해도 23년 통계를 확인했을 때 37%의 PWA만 서비스워커를 사용 중이다.
웹 컴포넌트는 “기능을 나머지 코드로부터 캡슐화하여 재사용 가능한 커스텀 엘리먼트를 생성하고 웹 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음”이라고 한다.
MDN 기준으로 custom elements, shadow DOM, HTML templates로 총 3개로 분류한다.
(www.webcomponents.org에서는 ES Module을 추가하여 총 4개로 분류한다)
Custom elements에도 두 가지 타입이 있다.
<p>태그에 어떤 기능을 추가<custom-button>이라는 새로운 tag를 개발두 방식 모두 아래와 같이 Class를 확장시키는 방식으로 정의를 한다
// Customized build-in elements
class WordCount extends HTMLParagraphElement {
constructor() {
super();
}
// Element functionality written in here
}
// Autonomous custom elements
class PopupInfo extends HTMLElement {
constructor() {
super();
}
// Element functionality written in here
}
개인적으로 vanilla 개발을 하거나, UI 라이브러리를 만들지 않는 이상 사용하지는 않을 것 같을 것 같다. GPT는 Safari가 Customized build-in elements을 지원하지 않기에 Autonomous custom elements를 추천하길 원하지만 프론트엔드에서 컴포넌트 단위로 개발을 주로 하기에 많이 쓰일 것 같지는 않다.
DOM트리에 영향을 주지 않고 독립적인 구조, 스타일, 이벤트를 가질 수 있게 해주는 DOM 트리를 생성하는 기술이라고 한다.
아래와 같은 형태로 표현되며 단어는 다음과 같다
// HTML
<div id="host"></div>
<span>I'm not in the shadow DOM</span>
// JS
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
const span = document.createElement("span");
span.textContent = "I'm in the shadow DOM";
shadow.appendChild(span);
위와 같이 원하는 node를 선택하고 shadow Tree를 코드로 생성하고 attachShadow를 통하여 붙일 수 있다.
캡슐화 되어 있는 트리에 JS로 접근을 하려면 attachShadow({ mode: "open" });와 같이 open을 설정하면 페이지에서 아래와 같이도 접근할 수 있다. 만약 접근을 원하지 않는다면 {mode: "closed"}로 설정하면 된다
// HTML
<div id="host"></div>
<span>I'm not in the shadow DOM</span>
<br />
<button id="upper" type="button">Uppercase shadow DOM span elements</button>
<button id="reload" type="button">Reload</button>
// JS
const host = document.querySelector("#host");
const shadow = host.attachShadow({ mode: "open" });
const span = document.createElement("span");
span.textContent = "I'm in the shadow DOM";
shadow.appendChild(span);
const upper = document.querySelector("button#upper");
upper.addEventListener("click", () => {
const spans = Array.from(host.shadowRoot.querySelectorAll("span"));
for (const span of spans) {
span.textContent = span.textContent.toUpperCase();
}
});
const reload = document.querySelector("#reload");
reload.addEventListener("click", () => document.location.reload());
아래 질문들은 GPT와 같이 공부하면서 얻은 질문들입니다
결론부터 말하자면 SEO에 걸리지 않는다.
왜냐하면 shadow DOM은 HTML 내부에 숨겨져 있으며 검색엔진이 찾지 못하기 때문이다.
따라서, 만약에 SEO가 필요한 값이라면 shadow DOM을 사용하는 것은 추천하지 않는다
결론부터 말하자면 open은 외부에서 컨트롤이 가능하기에 보안이 필요 없는 것에 사용하고 closed는 보안에 필요한 것에 사용하면 된다.
“C-2-1. Shadow DOM 만들어보기”에서 봤던 코드를 다시 보면 open으로 설정 시 page 내부 JS에서 접근이 가능하다. 이를 통해서 다른 사용자가 구조를 수정하고 확장할 수 있을 때 유용할 것이다.
그러나, 접근을 하지 않게 만드려면 closed를 사용하면 된다.
실제로 어느 부분에서 적용되고 우리가 혜택을 받고 있는 지 궁금해서 찾아봤다.
input 태그에 type=”rage”을 적용시키면 아래와 같이 나오며 slider가 shadow DOM으로 작성된 것을 알 수 있다.

shadow DOM에 대한 정보를 보고 싶으면 chrome developer tools의 설정에서 Preference 탭 아래 Elements 그룹 하위에 “Show user agent shadow DOM”을 클릭하면 아래와 같이 DOM tree 내부에서 볼 수 있다.

<input />이외에도 video의 버튼이나 슬라이더, <select /> 및 <option/> 또한 shadow DOM을 활용하여 만들어진 tag들이다
이 기술은 반복되는 markup을 표현하기 위해서 사용되는 기술이다.
<template>을 활용하여 작성할 수 있고 DOM에 렌더링 되지는 않지만 JS의 참조를 통해서 화면에 노출된다.
아래 MDN의 예제를 보면 활용법을 바로 알 수 있다.
// HTML
<template id="custom-paragraph">
<p>My paragraph</p>
</template>
// JS
let template = document.getElementById("custom-paragraph");
let templateContent = template.content;
document.body.appendChild(templateContent);
추가적으로 <slot />이라는 tag가 있는데 <template />와는 다르게 web components 내부에 동적으로 추가할 수 있는 공간을 만들어 놓는다고 한다.
<template />과 <slot />을 비교하자면 아래와 같다.
| 비교 항목 | template | slot |
|---|---|---|
| 역할 | 미리 정의된 정적 HTML을 DOM에 동적으로 삽입 | 컴포넌트 내부에서 동적 콘텐츠를 삽입할 자리 |
| 렌더링 여부 | 초기에는 렌더링되지 않음 (스크립트로 추가해야 함) | 부모 요소에서 삽입하면 자동으로 반영 |
| 사용 위치 | HTML 어디서든 사용 가능 | 웹 컴포넌트 내부에서만 사용 가능 |
| 주요 사용 예 | 템플릿을 미리 정의하고 필요할 때 복제하여 사용 | 웹 컴포넌트의 유연성을 높이기 위해 동적 콘텐츠 추가 |
<my-card>
<h2 slot="title">동적으로 넣을 제목</h2>
<p slot="content">이 내용은 slot을 통해 들어감</p>
</my-card>
<script>
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.card { border: 1px solid #ddd; padding: 10px; }
</style>
<div class="card">
<slot name="title"></slot> <!-- 동적 콘텐츠 삽입 공간 -->
<slot name="content"></slot> <!-- 동적 콘텐츠 삽입 공간 -->
</div>
`;
}
}
customElements.define("my-card", MyCard);
</script>
Cookie
Using HTTP cookies - HTTP | MDN
SameSite 쿠키 설명 | Articles | web.dev
PWA
Making PWAs installable - Progressive web apps | MDN
Web Components
Using custom elements - Web APIs | MDN