사용 중인 spring security 버전 확인 방법

  1. build.gradle 확인
  2. Project Structure > Libraries 에서 spring-security 관련 라이브러리들의 버전 확인

spring security 동작 원리

모든 http 요청 -> spring security filter chain -> 인가 규칙에 맞는 요청에 대해서만 controller 로 요청이 전달

소스코드 예시

public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        // 1. CORS(교차 출처 리소스 공유) 설정을 활성화하고, 커스텀 cors 설정을 주입합니다.
        .cors(cors -> cors.configurationSource(corsConfigurationSource))

        // 2. HTTP Basic 인증(브라우저 팝업 아이디/비밀번호 방식) 비활성화
        .httpBasic(httpBasic -> httpBasic.disable())

        // 3. CSRF(사이트 간 요청 위조) 방어기능 비활성화
        .csrf(csrf -> csrf.disable())

        // 4. 폼 로그인(기본 로그인 페이지) 비활성화
        .formLogin(formLogin -> formLogin.disable())

        // 5. 세션 사용 정책을 'STATELESS'(무상태)로 설정 (세션 저장 X, JWT처럼 토큰 인증을 쓸 때)
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

        // 6. 경로(URL) 별 인가(Authorization) 정책
        .authorizeHttpRequests(auth -> auth
            // 6-1. 아래 경로는 인증 없이 누구나 접근 허용
            .requestMatchers(
                "/api/v1/auth/login", 
                "/api/v1/chatbot/process-log", 
                "/api/v1/auth/change-password",
                "/api/v1/auth/reset-password", 
                "/api/v1/term",
                "/swagger-ui/**", 
                "/v3/api-docs/**"
            ).permitAll()
            // 6-2. 아래 경로는 로그인 인증한 사용자만 접근 허용
            .requestMatchers("/api/v1/auth/user-info").authenticated()
            // 6-3. 아래 경로는 ADMIN 역할을 가진 사용자만 접근 허용
            .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
            // 6-4. 그 외 나머지 모든 요청도 인증 필요(로그인 필요)
            .anyRequest().authenticated()
        );
    // 7. 커스텀 필터를 UsernamePasswordAuthenticationFilter 이전에 등록
    http.addFilterBefore(loggingApiKeyFilter, UsernamePasswordAuthenticationFilter.class);
    http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    // 8. 최종적으로 SecurityFilterChain을 build해서 빈으로 등록
    return http.build();
}

SecurityFilterChain

  .authorizeHttpRequests((auth) -> auth
    .requestMatchers("/public/**").permitAll() // 1. 누구나 접근
    .requestMatchers("/admin/**").hasRole("ADMIN") // 2. ADMIN만 접근
    .anyRequest().authenticated() // 3. 그 외는 로그인 필요
  );
  • authorizeHttpRequests → 인가 설정의 시작점, 체이닝 방식으로 여러 규칙 설정
  • spring6부터 authorizeReuqests 대신 authorizeHttpRequests 사용
// 1. 공개 경로 설정
.requestMatchers("/", "/login", "/signup").permitAll()


// 2. 특정 API 경로는 관리자만  
.requestMatchers("/admin/\*\*").hasRole("ADMIN")

// 3. 나머지는 로그인한 사용자만  
.anyRequest().authenticated()
  • requestMatchers : 어떤 url 패턴에 대해 규칙을 적용할지 지정
  • 괄호 안에 url 패턴(경로, 와일드카드 지정 가능)을 적으면, 그 url에 대한 권한을 바로 뒤에서 설정 (permitAll, hasRole 등)
  • 예전에는 antMatchers, 최신은 requestMatchers. 동작 방식은 비슷
  • 동작 순서: 요청 URL → 설정된 패턴과 매칭 → 허용/차단 결정
http.addFilterBefore(loggingApiKeyFilter, UsernamePasswordAuthenticationFilter.class);  
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  • addFilterBefore, addFilterAfter, addFilter: security filter chain에 커스텀 필터를 삽입하기 위한 HttpSecurity 객체의 메서드
  • 필터 순서 중요성: 인증(JWT, 세션 등) 필터가 인가(권한) 검사보다 먼저 실행되어야 함
  • 예외 처리, 로그 남기기 등은 시점에 따라 적절한 위치에 삽입 필요

정리

  • 설정 코드는 위에서 아래로 순서대로 적용
  • 필터 실행은 addFilter/체인 기준 순서로 동작
  • 경로별 인가(permitAll, authenticated, hasRole)는 필터 실행 이후 판단됨
  • 애플리케이션이 구동될 때 설정코드 및 필터 순서에 대한 필터 체인 구성
  • HTTP 요청이 들어올 때 마다 미리 정해진 필터 순서에 따라 필터 실행

'#개발 > spring' 카테고리의 다른 글

java.lang.NoClassDefFoundError  (0) 2025.10.17
spring-boot-devtools  (0) 2025.10.08
H2 Database  (0) 2025.10.08
Thymeleaf  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08

현상

런타임 오류: org.springframework.security.authentication.InternalAuthenticationServiceException: java.lang.NoClassDefFoundError: com/poc_lotte_chatbot/entity/QPartyRoleRelMstr

오류 원인 분석

NoClassDefFoundError

NoClassDefFoundError는 JVM이 클래스를 로드할 수 없을 때 발생합니다. 빌드 때는 있었지만 런타임 시 해당 클래스를 찾지 못할 때 발생

클래스 위치

문제의 클래스: com/poc_lotte_chatbot/entity/QPartyRoleRelMstr
보통 Q로 시작되는 클래스는 QueryDSL에서 자동 생성되는 entity 클래스

주요 원인

  1. QueryDSL 관련 클래스가 빠짐
  2. 의존성 누락
  • QueryDSL 관련 라이브러리 혹은 annotation processor build.gradle 또는 pom.xml에서 누락되었을 가능성
  1. 빌드 오류 혹은 클린 빌드 필요
  • 빌드 캐시 문제로 클래스가 생성되지 않은 채 빌드되었을 가능성

해결 방법

  1. QueryDSL Q-클래스 생성 확인
  • build/generated 폴더에서 QPartyRoleRelMstr 클래스 파일이 실제로 생성되었는지 확인
  1. 의존성 확인 및 Enable annotation processing

    dependencies {
     implementation 'com.querydsl:querydsl-jpa'
     annotationProcessor 'com.querydsl:querydsl-apt'
    }

settings > Build, Execution, Deployment > Compiler > Annotation Processors

```
3. 클린 빌드
Gradle: ./gradlew clean build
Maven: mvn clean install

'#개발 > spring' 카테고리의 다른 글

spring security 기본  (0) 2025.10.17
spring-boot-devtools  (0) 2025.10.08
H2 Database  (0) 2025.10.08
Thymeleaf  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08

useState: 값 저장 및 변경
useEffect: 부수효과 (API 등)
useContext: 전역 데이터 읽기
useRef: DOM 참조/기억
useMemo/useCallback: 성능 최적화
useReducer: 복잡한 상태변화

 


Hook 이름 사용 방법 주 사용 목적 동작원리 주요 사용 케이스
useState const [state, setState] = useState(초깃값); 간단 상태값/ 로직 상태값을 컴포넌트에 바인딩(초기값 등록). setter 호출 시 React가 상태 저장 및 리렌더링 숫자 카운터, 입력값 관리, 토글 등 간단한 상태 저장
useEffect useEffect(() ⇒ { effect }, [의존성]) 사이드이펙트(부수효과) 관리 렌더 후 사이드이펙트(부수효과) 실행. 의존성이 바뀌거나 mount/unmount 때 실행 API 콜, 타이머, 이벤트 구독/해제 
useContext const value = useContext(MyContext); 글로벌 상태, 전역 데이터 공급 Context.Provider에서 제공한 데이터를 쉽게 읽음 전역 상태, 언어 전역 설정, 테마 관리 등
최상위 부터 하위까지 prop drilling 없이 자식 요소까지 값 공유
useRef const ref = useRef(초깃값); 리렌더 필요 없이 "값/ DOM" 저장 변수를 리렌더링 없이 “참조(ref) 공간”에 저장. .current로 접근 DOM 요소 직접 조작,
이전 값 저장,
setTimeout 등 외부 값 저장
useMemo const value = useMemo(() ⇒ expensiveFn(), [의존성]); '비싼 계산 결과' 캐싱 특정 연산 결과를 의존성 배열 기준으로 “메모이제이션”(캐싱) 연산이 무거운 값,
불필요한 재계산 방지,
렌더 성능 최적화 필요할 때
useCallback const fn = useCallback(() ⇒ { ... }, [의존성]); '함수' 캐싱, 자식 렌더링 최적화 함수(콜백)를 “메모이제이션”(생성/바뀌는 타이밍 최소화) 자식에게 함수를 prop으로 넘길 때 불필요한 재생성 방지,
useMemo/ useEffect의 의존성 배열에 함수가 들어갈 때 렌더 최적화
(함수 생성 자체의 리소스 절약이 주목적이 아님)
useReducer const [state, dispatch] = useReducer(reducer, 초기값); 복잡 로직, 다양한 액션/ 상태 다양한 action에 따른 상태 업데이트 로직을 한 곳에(리듀서로) 집중 복잡한 상태 관리, 여러 액션/상태가 얽힌 폼 등에서 사용

1. useState

심화 설명

  • 컴포넌트 개별 상태값을 함수형 컴포넌트에서도 사용할 수 있게 하는 Hook
  • setter 함수 호출 시 React는 해당 컴포넌트만 리렌더
  • 상태값과 Setter는 컴포넌트마다 독립적으로 유지

코드 예제

import React, { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0); // count: 상태값, setCount: 상태 변경함수

  return (
    <div>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

설명:
클릭할 때마다 상태(count) 값이 변하고, 변할 때마다 화면이 다시 그려집니다.

 

2. useEffect

심화 설명

  • **렌더 후 수행되는 "부수 효과(side-effect)"**를 관리
  • 컴포넌트가 마운트/업데이트/언마운트 되는 시점을 제어 가능
  • 의존성 배열 값을 기준으로 언제 effect를 실행할지 설정

코드 예제

import React, { useState, useEffect } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => setSeconds(s => s + 1), 1000);
    // 언마운트 시 타이머 제거
    return () => clearInterval(timer);
  }, []); // []: 최초 1회만 실행 (마운트/언마운트)

  return <div>{seconds}초 지남</div>;
}

설명:
setInterval로 초를 증가시키되, 컴포넌트가 사라질 때(return) 타이머 삭제

 

3. useContext

심화 설명

  • 컴포넌트 트리 전체에 데이터(전역 상태 등)를 prop drilling 없이 공급
  • 값을 읽어올 때마다 최신 컨텍스트 값을 자동 반영
  • 주로, 테마, 로그인 정보, 언어 설정 등에 사용

코드 예제

import React, { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function ThemedButton() {
  const theme = useContext(ThemeContext); // 컨텍스트 값 읽기
  return <button style={{ background: theme === "dark" ? "#222" : "#eee" }}>Button</button>
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

설명:
ThemedButton은 ThemeContext의 값을 받아 버튼의 스타일을 자동으로 변경합니다.

 

 

5. useMemo / useCallback

심화 설명 (둘의 차이)

  • useMemo: 비싼 연산(무거운 계산 결과)를 캐시
  • useCallback: 함수를 캐시 (같은 함수가 자식에 계속 전달될 때 불필요한 렌더 방지)
    • props로 함수를 전달할 때 (특히 React.memo/자식컴포넌트에)
      • 함수 주소가 바뀌지 않게 하려면 useCallback 필요
    • 의존성 배열에 함수가 들어가는 useEffect/useMemo 사용할 때
    • 많은 수의 컴포넌트 리렌더링/최적화가 중요한 경우
    • 불필요하게 모든 함수에 useCallback을 남용하면, 메모리와 관리 비용이 오히려 늘 수 있음
    • 부모의 state/props를 사용하는 함수일 경우, 그 값이 변하면 useCallback도 새 함수가 됨
      (의존성 배열에 값을 꼭 명확히 넣어야 함)

useMemo 코드 예제

const expensiveValue = useMemo(() => {
  return heavyComputation(num); // num 바뀔 때만 재계산
}, [num]);

설명:
num 값이 바뀔 때만 heavyComputation() 실행, 그 외 캐시 사용.

1. 대규모 리스트 필터링 & 소트 (비효율적 렌더링 방지)

대규모 데이터 배열에서 필터링과 정렬 같은 연산이 자주 일어날 때, 상태(state)가 변할 때마다 무조건 연산하면 성능에 치명적입니다.
이럴 때 useMemo로 의존성 값이 바뀔 때만 계산하도록 만들어 렌더 성능을 최적화합니다.

예시 코드

import React, { useState, useMemo } from "react";

// 수천 개의 유저 리스트가 있다고 가정
const userList = [...Array(10000)].map((_,i) => ({
  id: i, name: `User ${i}`, age: Math.floor(Math.random() * 80)
}));

function UserListApp() {
  const [keyword, setKeyword] = useState("");
  const [sortByAge, setSortByAge] = useState(false);

  // userList 필터링 & 정렬 연산을 useMemo로 캐싱
  const filteredSortedUsers = useMemo(() => {
    let list = userList.filter(user => user.name.toLowerCase().includes(keyword.toLowerCase()));
    if (sortByAge) list = list.sort((a,b) => a.age - b.age);
    console.log('expensive filtration!'); // 실제 렌더링 횟수 확인용
    return list;
  }, [keyword, sortByAge]); // keyword나 sortByAge가 바뀔 때만 재계산

  return (
    <div>
      <input placeholder="Search user" value={keyword}
        onChange={e => setKeyword(e.target.value)} />
      <button onClick={() => setSortByAge(s => !s)}>
        {sortByAge ? "나이순 정렬 해제" : "나이순 정렬"}
      </button>
      <ul>
        {filteredSortedUsers.map(user =>
          <li key={user.id}>{user.name} ({user.age})</li>
        )}
      </ul>
    </div>
  );
}

설명:

  • 컴포넌트 리렌더: 모든 state 변경 시 항상 발생
  • useMemo 계산: 오직 의존성 배열 항목(keyword, sortByAge)이 바뀔 때만 발생
  • 나머지 경우: 아무리 컴포넌트가 리렌더돼도 useMemo 결과(비싼 연산)는 재사용됨

이렇게 불필요한 연산 제외로 대규모 데이터 처리 시 성능을 크게 향상시킬 수 있습니다.

 

 

useCallback 코드 예제

const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []); // 의존값이 없으면 최초 1회만 생성

// 자식에 handleClick 등 함수를 prop으로 줄 때, 불필요한 재생성 방지
  • useEffect/useMemo의 의존성 배열 안에 함수가 들어가고, 그 함수가 리렌더마다 새로 만들어질 수 있다면 → useCallback으로 감싸주는 게 필수!
  • 그렇지 않으면, 본래 의도와 다르게 useEffect/useMemo가 과도하게 실행됨
function Parent({ value }) {
  // 매 렌더마다 handleChange는 새로운 함수임
  const handleChange = (v) => {
    console.log("changed:", v);
  };

  useEffect(() => {
    // handleChange가 새 함수로 인식돼서, value와 상관없이 매번 실행됨
    handleChange(value);
  }, [value, handleChange]); 
  // 여기서 handleChange는 매번 새 함수라 useEffect가 매번 작동

  return <div>{value}</div>;
}
  • 문제점:
    • value가 변하지 않아도, Parent가 리렌더될 때마다 handleChange가 바뀌므로
    • useEffect가 쓸데없이 반복 실행
function Parent({ value }) {
  // useCallback으로 함수 재사용 (value가 변할 때만 새로 만듦)
  const handleChange = useCallback((v) => {
    console.log("changed:", v);
  }, []);

  useEffect(() => {
    handleChange(value);
  }, [value, handleChange]);
  // 이제 handleChange가 변하지 않으므로, useEffect는 value 변화시에만 실행

  return <div>{value}</div>;
}
  • 해결:
    • handleChange가 의미 있게 바뀔 때만 새로 만들어짐
    • useEffect가 불필요하게 반복 실행되지 않음

 

 

 

6. useReducer

심화 설명

  • 복잡 상태(여러 값, 다양한 action, 상태 전이 필요 시) 사용
  • reducer 함수 형태로 state, action에 따라 새 상태를 산출
  • Redux 패턴의 간소화 버전

코드 예제

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

설명:
상태(state), action(무엇을 할지)에 따라 상태 관리. 복잡한 폼, 여러 입력값, 다양한 이벤트 트리거에 적

템플릿 파일을 수정할 때마다 서버 재실행하는 게 매우 번거로움.
build.gradle > dependencies 에 spring-boot-devtools 속성만 추가(이후, Gradle 동기화)해주면, html 파일을 컴파일만 해주면 서버 재기동 없이 View 파일 변경이 가능하다.

 

재실행 시, main -> restartedMain 으로 표시됨

 

템플릿 파일 수정 후 해당 파일만 '다시 컴파일' 실행

'#개발 > spring' 카테고리의 다른 글

spring security 기본  (0) 2025.10.17
java.lang.NoClassDefFoundError  (0) 2025.10.17
H2 Database  (0) 2025.10.08
Thymeleaf  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08

JPA 환경 설정

Hibernate는 SQL 쿼리를 생성해주는 기능을 가지고 있는데, Mysql, Oracle 등 데이터베이스 마다 문법이 조금씩 다르기 때문에 Dialect(방언)을 설정해주어야한다. 그렇지 않으면 런타임 오류 발생

application.yml 에 dialect 지정

jpa:
  hibernate:
    ddl-auto: create
  properties:
    hibernate:
      #        show_sql: true
      format_sql: true
      dialect: org.hibernate.dialect.H2Dialect

Spring Boot 권장 방식 활용

Spring Boot는 Jpa Provider가 Dialect를 자동으로 설정할 수 있도록 하는 것을 권장합니다. 따라서 Dialect를 명시적으로 설정하지 않아도 되는 경우가 많으므로, Spring Boot의 자동 설정을 활용하는 것이 더 좋습니다

'#개발 > spring' 카테고리의 다른 글

java.lang.NoClassDefFoundError  (0) 2025.10.17
spring-boot-devtools  (0) 2025.10.08
Thymeleaf  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08
[환경설정] 웹 애플리케이션 프로젝트의 기본 의존성  (0) 2025.10.03

Thymeleaf의 특징

- 코드 자체가 브라우저에서 동작 (JSP 는 동작X)  
- HTML 마크업의 형식을 깨지 않고 논리 코드 작성 가능 (JSP처럼 if 같은 키워드를 사용하지 않음)

2.x의 치명적인 단점

- 태그를 열면 반드시 태그를 닫아주어야 하는 불편함 (ex.  <br> </br>) -> 3.x 에서 개선됨

스프링 부트 thymeleaf viewName 매핑 (스프링부트 메뉴얼 참고하여 prefix, suffix 수정 가능)

resouces: templates/ + {Viewname}+.html

폴더 역할

static -> 정적 파일 (ex. index.html)
templates -> 템플릿엔진을 통해 렌더링해야할 파일 (ex. hello.html)

spring-boot-devtools 의존성 추가

서버 재구동 없이 특정 템플릿만 다시 컴파일하여 수정 반영해주는 도구

'#개발 > spring' 카테고리의 다른 글

spring-boot-devtools  (0) 2025.10.08
H2 Database  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08
[환경설정] 웹 애플리케이션 프로젝트의 기본 의존성  (0) 2025.10.03
spring 개발 작업  (0) 2025.09.29
  1. 프로젝트 환경설정
  • 스프링 프로젝트 생성(의존성 추가)
  • 심플 View API 구현 (/hello)

'#개발 > spring' 카테고리의 다른 글

H2 Database  (0) 2025.10.08
Thymeleaf  (0) 2025.10.08
[환경설정] 웹 애플리케이션 프로젝트의 기본 의존성  (0) 2025.10.03
spring 개발 작업  (0) 2025.09.29
application.properties - spring.datasource  (0) 2025.09.23

의존성 확인 방법(가독성 좋게)

Gradle > project명 > SourceSets
- SourceSets는 build.gradle에 별도 설정 필요

핵심 라이브러리

스프링 MVC,
스프링 ORM
JPA, 하이버네이트 스프링 데이터 JPA
- 참고: 스프링 데이터 JPA는 스프링과 JPA를 먼저 이해하고 사용해야 하는 응용기술이다.

기타 라이브러리

H2 데이터베이스 클라이언트
- 데이터베이스 버전과 클라이언트 버전이 불일치 시에 오류가 발생하는 경우가 존재

커넥션 풀: 부트 기본은 HikariCP
WEB(thymeleaf)
로깅 SLF4J & LogBack
테스트

'#개발 > spring' 카테고리의 다른 글

Thymeleaf  (0) 2025.10.08
프랙티스 가이드  (0) 2025.10.08
spring 개발 작업  (0) 2025.09.29
application.properties - spring.datasource  (0) 2025.09.23
클라이언트 요청 받을 때, 데이터를 같이 받는 방법  (0) 2025.09.14

1. 프로젝트 환경설정

2. 요구사항 분석

3. 도메인, 엔티티, 테이블 설계

4. 아키텍처 구성

5. 핵심 비즈니스 로직 개발 - 회원, 상품, 주문 도메인 개발->핵심 비즈니스 로직 개발->테스트케이스 검증->DDD 이해

6. 테스트

7. 웹 계층 개발

웹 애플리케이션 구조: MPA 와 SPA

MPA (Multi Page Application)

  • 전통적인 웹사이트 방식
  • 사용자가 어떤 링크를 클릭하면 서버에 요청을 보내고, 서버가 새로운 HTML 페이지를 만들어서 브라우저에 전달
  • 페이지 이동 시마다 서버에 새 요청 -> 서버가 새 HTML 생성 -> 브라우저가 전체 새로고침
  • 기본 철학: 서버가 모든 것을 처리하고, 클라이언트(브라우저)는 결과만 보여줌.
  • 장점: SEO(검색엔진 최적화)에 유리, 보안 및 유지보수 용이.
  • 단점: 페이지 이동 시마다 새로고침이 일어나 느리고 부자연스러움.
  • 화면이 그려지는 과정:
    1. [클라이언트] 사용자가 URL 입력/클릭 + 서버에 HTTP 요청 전송
    2. [서버] 요청받은 URL에 맞는 HTML 전체 페이지를 생성/전송 + 정적 리소스(CSS,JS,이미지)경로 전달
    3. [클라이언트] 응답받은 HTML, CSS, JS를 새로 로드(페이지 전체 새로고침)
    4. [클라이언트] 새 페이지 렌더링

SPA (Single Page Application)

  • 한 개의 HTML 페이지에서 모든 기능을 처리
  • 페이지 이동이 필요할 때, 필요한 데이터만 서버에서 받아와서 화면 일부만 동적으로 바꿈
  • 전체 페이지 새로고침 없이 부드럽게 동작
  • 최초 1회만 전체 리소스(HTML, JS, CSS) 로드
  • 서버는 index.htmlJS/CSS 번들만 제공.
  • 실제 각 페이지의 HTML은 JS가 브라우저에서 동적으로 생성 (브라우저 새로고침 없음)
  • 대표적인 예: React, Vue, Angular 등으로 만든 웹앱.
  • 등장 배경: MPA의 느린 사용자 경험을 개선하기 위해 등장.
  • 장점: 앱처럼 부드럽고 빠른 UX를 제공하고자 함.
  • 단점: 초기 로딩이 느릴 수 있고, SEO에 불리함(검색엔진이 자바스크립트 렌더링을 잘 못함).
  • 화면이 그려지는 과정:
    1. [클라이언트] 사용자가 URL 입력/클릭 + 서버에 HTTP 요청 전송
    2. [서버]SPA의 기본 HTML, CSS, JS 번들을 전송 (대부분 빈 HTML + JS)
    3. [클라이언트] 뼈대 렌더링
    4. [클라이언트] JS실행되어 실제화면 생성 및 렌더링
      렌더링 과정 : HTML 파싱->DOM트리 생성->최초 렌더링

렌더링 방식: SSR 과 CSR

SSR (Server Side Rendering)

  • 서버에서 HTML을 미리 만들어서 클라이언트에 전달.
  • 사용자는 즉시 완성된 화면을 볼 수 있음.
  • 이후에는 SPA처럼 동작할 수 있음(예: Next.js, Nuxt.js).
  • 렌더링 과정:
    1.  **서버**가 자바스크립트(혹은 템플릿 엔진 등)를 실행해서,
    2. 서버에서 **API 요청**을 보내 데이터를 받아오고,
    3. 받은 데이터를 이용해 **서버에서 HTML을 만듭니다**.
    4. 만들어진 HTML을 브라우저에 보내주면,
    5. 브라우저는 받은 HTML을 바로 보여줍니다
  • 등장 배경: SPA/CSR의 단점(초기 로딩 속도, SEO 문제)를 보완하기 위해 등장.
  • 장점: 초기 화면을 빠르게 보여주고, 검색엔진에도 잘 노출되도록 함.
  • 단점: 서버에 부하가 더 걸릴 수 있음, 구현이 복잡해질 수 있음.

CSR(Client Side Rendering)

  • 브라우저(클라이언트)에서 자바스크립트로 화면을 그리는 방식.
  • 서버는 데이터(API)만 제공하고, 실제 화면은 클라이언트가 만듦.
  • SSR은 CSR에서 브라우저가 하던 "API로 데이터 받아서 화면을 그리는" 작업을 서버가 대신해서, 완성된 HTML을 브라우저에 보내주는 방식
  • 등장 배경: SPA의 구현 방식으로, 서버의 부담을 줄이고, 클라이언트의 성능을 활용.
  • 단점: 초기 로딩 시 자바스크립트 파일을 모두 받아야 하므로 느릴 수 있음. SEO에 불리.

MPA,SPA,SSR, CSR 개념 관계

1) MPA와 SPA는 애플리케이션 구조에 대한 분류
MPA: 여러 페이지(HTML)로 구성
SPA: 한 페이지(HTML)에서 동적으로 화면 전환
2) SSR과 CSR은 렌더링 방식에 대한 분류
SSR: 서버에서 HTML 생성
CSR: 클라이언트에서 HTML 생성

활용 방식

1) MPA ↔ SSR/CSR
MPA는 전통적으로 SSR과 함께 사용됨.
각 페이지 요청 시 서버가 HTML을 만들어서 내려줌.
하지만, MPA에서도 일부 CSR(자바스크립트로 동적 UI)을 사용할 수 있음.
예: jQuery로 일부 동적 UI 구현
2) SPA ↔ SSR/CSR
SPA는 전통적으로 CSR과 함께 사용됨.
최초에 빈 HTML과 JS를 받아오고, 이후 화면은 JS가 만듦.
하지만, 최근에는 SPA에서도 SSR을 지원하는 프레임워크가 많음.
예: Next.js(React 기반), Nuxt.js(Vue 기반) 등
서버에서 초기 HTML을 렌더링해서 보내고, 이후에는 CSR로 동작

+ Recent posts