웹 요청 방식 비교 분석 Form vs Fetch API

두 방식의 네트워크 계층과 브라우저 처리 관점의 차이를 알아보자 (ft. GET/POST Form 차이)

September 17, 2025

배경

프로젝트에서 페이지마다 SPA 방식정적 html로 제공하는 MPA 방식으로 구현을 나눠서 진행하며 하이브리드 방식을 적용하고 있다.

구체적으로는,

  • MPA: 로그인 페이지와 회원가입 페이지는 요청에 따라 서버가 정적 HTML로 응답해주는 방식으로 구현한다. 라우팅(/login, /register)에 따라 분기처리해주고, body에 해당 URI에 맞는 html 파일을 반환하도록 했다.

  • SPA: 글 작성 페이지와 마이페이지는 클라이언트에서 SPA 방식으로 라우팅을 구현하고 Vite로 빌드를 진행한다.

이때 서버에서 생성된 회원가입 페이지를 HTML Form을 fetch API를 사용하지 않고 순수 HTML Form 방식으로 전송하도록 해야한다.

평소라면 fetch API를 많이 활용했었는데 이 두 가지 방식의 차이점은 뭘까? 프로젝트를 진행하기 위해 선행될 지식이기 때문에 정리해보자.

1. 개요 - 무엇이 다른가?

단순 HTML <form> 전송 방식과 fetch API 전송 방식은 기능적으로는 "서버로 HTTP 요청을 보낸다"는 점에서 같지만, 네트워크 계층 동작 방식과 브라우저 처리 관점에서 중요한 차이가 있다.

핵심 차이점

  • 요청 생성 주체: 브라우저 자동 생성 vs 개발자 직접 제어
  • 응답 처리 방식: 브라우저의 페이지 전환 vs 자바스크립트 코드 처리
  • 사용자 경험(UX): 전체 페이지 새로고침 vs 부분적 동적 업데이트

이 두 방식의 근본적인 차이점을 가지고 자세히 알아보겠다.

2. 요청(Request) 생성 방식

브라우저와 서버가 통신하기 위해서는 HTTP 스펙에 맞는 요청과 응답을 해야한다. 먼저 브라우저에서는 표준 헤더가 담긴 요청을 서버에게 보낸다. 이때 요청 방식에 따라 브라우저에서 작동하는 방식과 직접 제어하는 방식으로 나뉜다.

HTML Form

  • 브라우저가 자동으로 HTTP 요청 메시지를 구성한다.
    • method="GET"이면 Query String
    • method="POST"이면 application/x-www-form-urlencoded 기본 인코딩
  • 기본적으로 브라우저가 표준 헤더(Content-Type, Accept) 등을 자동 세팅한다.
  • 개발자가 세밀하게 헤더나 요청 바디를 통제하기 어렵다.

fetch API

  • 개발자가 요청을 직접 제어한다.
  • HTTP 메서드, 헤더, 바디(JSON, FormData, Blob 등)를 자유롭게 지정 가능하다.
  • 쿠키/인증 정보를 포함할지(credentials 옵션) 등 네트워크 계층의 동작을 선택적으로 제어할 수 있다.

3. 응답(Response) 처리 방식

서버는 브라우저에게 받은 요청에 따른 응답을 해준다. 이때 브라우저의 응답은 서버에서 페이지를 직접 던져주면서 새로 로드되거나, 비동기 객체를 사용하여 페이지가 새로 로드되지 않고 자바스크립트에서 세부적인 컨트롤이 가능한 방식으로 나뉜다.

HTML Form

  • 서버 응답(HTML, Redirect 등)은 브라우저가 새 페이지로 로드하거나 리다이렉트 처리한다.
  • 즉, 브라우저 렌더링 파이프라인에 직접 연결 → 응답은 자동으로 화면 전환으로 이어진다.
  • 응답을 자바스크립트 코드로 가공할 수는 없다.

fetch API

  • 응답이 Promise<Response> 객체로 전달된다.
  • 응답 본문을 response.json(), response.text() 등으로 직접 파싱해 사용자가 원하는 로직으로 처리할 수 있다.
  • 화면 전환은 자동으로 일어나지 않으며, SPA 방식에서 필요한 UI만 갱신이 가능하다.

HTML Form 방식이 자바스크립트 개입이 어려운 이유

<form>의 전송 결과는 브라우저 렌더링 파이프라인으로 바로 들어간다.

(응답 본문 = 새 페이지로 렌더링) 응답 데이터를 JS 코드에서 직접 가공하거나 조건에 따라 분기 처리하는 것은 불가능하다.

따라서 "조건에 따라 다른 페이지로 보낸다" 같은 로직은 서버에서 결정해야 한다.

HTML Form 전송의 Redirect 처리 흐름

Form 전송 후 서버가 3xx 상태 코드(302 Found, 303 See Other 등)를 응답하면, 브라우저는 자동으로 Location 헤더에 지정된 URL로 이동한다. 이 과정에서 개발자가 중간에 자바스크립트로 개입할 수 없다. 즉, 리다이렉트를 서버가 제어해야 한다.

HTTP/1.1 302 Found
Location: /home  → 브라우저는 /home으로 자동 이동된다!

  • 메서드 변환: 302/303이면 보통 GET으로 전환
  • 전형적 PRG(Post/Redirect/Get) 패턴에 적합

fetch의 Redirect 처리 (브라우저 기준)

fetch는 응답이 JS로 전달되고, 서버가 3xx 응답을 보내면 브라우저 fetch가 내부적으로 그 리다이렉트를 따라가서 최종 응답까지 자동으로 받아온다.

const res = await fetch("/login", { method: "POST" }); // follow(기본)
if (res.redirected) {
  // 필요하면 여기서 직접 네비게이션
  window.location.href = res.url;
}

  • 기본값 redirect: "follow"으로 자동 추적하고, 중간의 3xx 응답은 JS에 노출되지 않고, Promise는 최종 응답으로 resolve 된다.
  • 브라우저에선 보안 때문에 헤더(Location)를 노출하지 않는다. 바디도 접근 불가하다.
  • 페이지 네비게이션은 일어나지 않는다. 단지 네트워크 레벨에서 추가 요청을 수행해 최종 리소스를 받아올 뿐이고, 화면 전환은 location.href 등으로 직접한다.

4. 브라우저 동작과 UX

위의 응답에 따라 브라우저의 동작 과정이 달라지고 사용자 경험까지 달라지게 된다.

HTML Form

  • 기본 동작: 요청을 보내고 응답에 따라 페이지 리로드(Full reload)
  • 사용자는 새로고침(깜빡임) 경험을 겪는다.
  • History API와 결합하지 않으면 SPA 같은 UX를 만들기 어렵다.

fetch API

  • 요청-응답이 백그라운드 비동기 처리
  • 페이지 전환이 일어나지 않음 → 기존 화면 상태 유지 가능
  • 부분 렌더링이나 동적 업데이트에 유리하다

5. 네트워크 계층 관점 요약

구분HTML Formfetch API
요청 생성브라우저가 자동 조립 (제한적 제어)개발자가 완전 제어 가능
응답 처리브라우저가 페이지로 렌더링JS 코드에서 직접 가공
네트워크 연결쿠키/세션 자동 포함credentials 옵션으로 통제
UX 영향전체 페이지 리로드페이지 유지 + 부분 업데이트

6. 결론

  • HTML Form: 전통적인 MPA (Multi-Page Application) 패턴에 적합하며, 서버가 렌더링을 주도하고 브라우저가 자동으로 처리하는 방식.
  • Fetch API: 현대적인 SPA (Single-Page Application) 패턴의 핵심으로, 클라이언트(개발자 코드)가 요청과 응답을 세밀하게 제어하는 방식.

두 방식은 동일한 HTTP 계층을 사용하지만, '응답 처리 주체''화면 갱신 방식' 에서 본질적인 차이를 보인다.

+ 회원가입 GET 방식 구현해보기

요구사항에 따라 회원가입 진행 시 <form> 폼을 사용해서 get method를 사용했다.

<form id="registerForm" class="form" method="get" action="/create">
  • action은 사용자의 입력값이 전달될 URL을 지정한다.
  • method를 get 또는 post로 지정하여 사용자의 입력값이 전달되는 방법을 결정할 수 있다.
  • get 방식: 주소창에 /create?userId=...&password=...&name=...&email=... 형식으로 입력값이 변수명과 변수값의 쌍(key-value) 형식으로 전달된다. 주소창으로 사용자 입력 값이 드러나는 보안에 취약한 형식이며 원래는 정보를 조회할 때 사용된다.
  • posts 방식: 입력값을 외부로 드러내지 않고 서버로 전달되며 get에 비해 상대적으로 덜 취약하다. 그래서 주로 '삽입, 갱신, 삭제' 용도로 사용한다.

여기까지 설정하고 회원가입을 진행하면 action와 method 속성으로 인해 다음과 같은 url로 이동하게 된다.

http://localhost:5173/create?userId=ekdus&name=dayeon&password=1234&email=kdy37912%40naver.com

여기서 로그인 화면으로 넘어가야 한다.

  • 따라서 /create로 동적 라우팅이 일어난 후,
  • 서버에서 회원가입 생성 처리가 진행되고, 헤더에서 /login 페이지로 302 리다이렉트 되도록 설정했다.
if (urlPath.startsWith("/create")) {
  const headers =
    `HTTP/1.1 302 Found\r\n` +
    `Location:  /login\r\n` +
    `Content-Length: 0\r\n` +
    `\r\n`;
  return { header: headers, body: "" };
}

+ 회원가입 POST 방식 구현해보기

내부적으로 form에서 보내는 POST 요청은 다음과 같다.

<form id="registerForm" class="form" method="post" action="/api/create">

Headers

POST /api/create HTTP/1.1
Host: localhost:5173
Content-Length: 65
Content-Type: application/x-www-form-urlencoded
...

Body

userId=ekdus&name=dayeon&password=1234&email=kdy37912%40naver.com

POST 요청은 GET 요청과 유사하지만 Body가 있다는 차이점이 있다. 그리고 Content-Length 헤더는 필수 항목이다.

이때 input 엘리먼트의 name 어트리뷰트와 value 어트리뷰트를 담은 body 내용은 내부적으로 폼 인코딩이 된 상태이다.

폼 인코딩이란 POST 요청을 통해 name-value 쌍의 데이터를 전송할 때 그 데이터를 특정한 형식으로 변환하는 것을 말한다.

기본적으로 한 쌍은 name=value 형태로 이뤄지고 각 쌍은 앰퍼샌드(&)로 연결한다.

다음 포스팅에서는 두 방식을 토대로 HTTP를 학습한 것을 바탕으로 직접 low 레벨로 웹 서버를 구현하도록 하겠다!