<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>굴러라</title>
    <link>https://brandofme.tistory.com/</link>
    <description>비전공(국문과) 개발자 지망생의 내멋대로 운영하는 velog</description>
    <language>ko</language>
    <pubDate>Mon, 6 Apr 2026 13:49:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>now.lee</managingEditor>
    <image>
      <title>굴러라</title>
      <url>https://tistory1.daumcdn.net/tistory/3567871/attach/15f4283da40149dcafbb02bdea2e04fa</url>
      <link>https://brandofme.tistory.com</link>
    </image>
    <item>
      <title>Next.js + tailwindCSS로 모달컴포넌트 만들기</title>
      <link>https://brandofme.tistory.com/entry/Nextjs-tailwindCSS%EB%A1%9C-%EB%AA%A8%EB%8B%AC%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;h1&gt;고민&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 모달 창들을 한번에 통합해서 만들 수 있는 컴포넌트를 만드는 게 과제였다.&lt;br /&gt;모달의 내부 디자인이 제각각 다르고 사이즈도 다른데다가 반응형을 고려하여 디자인해야 하는 만큼 어디까지 공통으로 뺄 수 있을까&lt;br /&gt;고민을 많이했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 바깥이 배경이 조금 어둡게 처리되고 해당 영역을 클릭했을 때 창이 닫히게 하는 것을 부모가 아닌 컴포넌트 영역에서 잡아주는 게&lt;br /&gt;더욱 편리할 것이라 생각했고 그 방안을 모색해야 했다.&lt;/p&gt;
&lt;h1&gt;1차시도: createPortal&lt;/h1&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;SomeComponent /&amp;gt;
  {createPortal(children, domNode, key?)}
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createPortal을 사용하면 일부 자식을 DOM의 다른 부분으로 렌더링할 수 있다.&lt;br /&gt;children 즉, 리엑트 노드 컴포넌트를 받아서, domNode에서 렌더링할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function MyComponent() {
  return (
    &amp;lt;div style={{ border: '2px solid black' }}&amp;gt;
      &amp;lt;p&amp;gt;This child is placed in the parent div.&amp;lt;/p&amp;gt;
      {createPortal(
        &amp;lt;p&amp;gt;This child is placed in the document body.&amp;lt;/p&amp;gt;,
        document.body
      )}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 입력하면 children이 컴포넌트 영역이 아닌, 전체 도큐먼트 영역으로 잡혀서 생성할 수 있으니 전체 화면을 잡아서&lt;br /&gt;띄워야 하는 모달 컴포넌트를 만들 때 좋다. 공식 문서에서도 모달을 만드는 예시가 제공되어있다.&lt;br /&gt;또한 react 컴포넌트를 react가 아닌 정적, 서버 렌더링 페이지의 일부에 집어넣을 수 있고, dom 노드로 렌더링하여 외부 dom 노드의 콘텐츠&lt;br /&gt;관리용으로도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 해당 방식으로 모달을 생성할 경우 next.js의 서버 컴포넌트와 연결할 때 mounting되는 시점 (즉, dom이 생성된 시점에 실행되어야 하는데)&lt;br /&gt;이것을 보장못하는 에러가 발생했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const [isMounted, setIsMounted] = useState(false);
useEffect(() =&amp;gt; { setIsMounted(true); }, []);

if (!isMounted || !isOpen) return null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect를 활용하여 mount된 시점에만 실행되도록 보장해줘야 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2차 시도: 부모 컨테이너 기준으로 고정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 개발하는 환경 자체가 웹으로 가동되는 모바일 환경을 흉내내는 화면 (즉 화면 max-h 등을 layout에서 고정시켜서 활용)하는&lt;br /&gt;화면이었기 때문에 반응형이라고는 해도 어느정도의 고정된 화면 사이즈가 있었다.&lt;br /&gt;해당 사이즈가 있음을 염두에 뒀을 때 부모 컨테이너의 사이즈를 기준으로 고정하는 것이 SSR처리가 사라져 간략화된다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 해당 방식으로 구현했을 경우에는 부모에서 영역을 잡아줘야 했다.&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;relative overflow-hidden&quot;&amp;gt;
  &amp;lt;PageContent /&amp;gt;
  &amp;lt;Modal isOpen={isOpen} onClose={...} /&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더위치는 컴포넌트 트리 그대로라는 단점이 있긴 하지만 실기기 사이즈가 아닌 임의 고정 사이즈로 개발하는 환경에서는 이게 더 외부에서&lt;br /&gt;사용하기 편리했다.&lt;/p&gt;
&lt;h1&gt;모달창 규격화 고민&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 children으로 내부를 밖에서 정의할 수 있도록 하고,&lt;br /&gt;내부에 있는 요소는 오직 카드형 모달 창과 버튼 영역만 잡았다. 버튼은 별도 컴포넌트로 만든 후 variant를 받아 적용할 수 있게 했고&lt;br /&gt;버튼이 1개만 있을 때, 버튼이 2개 있을 때, 버튼이 없을 때로 나누고 또 버튼이 2개 있을 경우 비대칭인 경우가 있어서 flex로 잡아놨다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;export interface ModalProps {
  isOpen: boolean;
  onClose?: () =&amp;gt; void;
  closeOnOverlay?: boolean;
  children?: ReactNode;
  buttonVariant?: 'single' | 'double' | 'AsymmetricDouble' | 'none';
  confirmText?: ReactNode;
  cancelText?: ReactNode;
  onConfirm?: () =&amp;gt; void;
  onCancel?: () =&amp;gt; void;
  primaryButtonClassname?: string;
  isHighlightButton?: boolean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 props만 따져도 이정도...라는 것이다.&lt;br /&gt;일단 열고 닫는 boolean, 닫혔을 때 받아야 하는 onClose, onConfirm, onCancel같은 버튼마다의 function과 buttonVariant (영역잡기)&lt;br /&gt;className과 isHighlightButton 같은 소소한 것까지.&lt;br /&gt;프라이머리 버튼도 꾸밀 수 있게 해줬다. (취소는 그냥 공통이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 실제 사용할 때 필수 입력값 자체는 얼마 되지 않는다.&lt;br /&gt;isOpen만 있어도 작동한다.&lt;br /&gt;그렇지만 일단 이것저것 커스텀 할 게 너무 많아서 이게 이걸로 괜찮은지 고민이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 모달 창 자체와 기본형 버튼 제공 + 뒤에 배경 선택 시 창닫기는 기본으로 제공된다.&lt;br /&gt;그러니 모달 내부에서도 공통으로 사용하는 경우 2차 컴포넌트를 만들어 재활용을 하는 방식으로 확장하려고 하지만&lt;br /&gt;역시 조금 어색하나 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 기본적으로 제공하는 padding속성들은 외부 조절까지 시키면 너무 props만 느는 것 같아 기본 제공하고&lt;br /&gt;docstring으로 적어두어 활용하여 2차 컴포넌트 개발을 조금이라도 쉽게 하게 하려고 했지만 역시 너무 복잡하진 않을까 고민이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀 코드&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;/** biome-ignore-all lint/a11y/noStaticElementInteractions: Modal 외부 클릭을 하였을 시 모달이 닫히도록 설정하기 위함*/
/** biome-ignore-all lint/a11y/useKeyWithClickEvents: 버튼이 아닌 걸 클릭하게 만들면 키보드 이벤트도 추가해야 한다는 건데 추가해도 반응하지 않습니다. */
'use client';

import { type ReactNode, useEffect } from 'react';
import { cn } from '@/lib/utils';
import { default as ModalButton } from './ModalButton';

export interface ModalProps {
  isOpen: boolean;
  onClose?: () =&amp;gt; void;
  closeOnOverlay?: boolean;
  children?: ReactNode;
  buttonVariant?: 'single' | 'double' | 'AsymmetricDouble' | 'none';
  confirmText?: ReactNode;
  cancelText?: ReactNode;
  onConfirm?: () =&amp;gt; void;
  onCancel?: () =&amp;gt; void;
  primaryButtonClassname?: string;
  isHighlightButton?: boolean;
}

/**
 * 범용 모달 컴포넌트
 *
 * 부모 컨테이너를 기준으로 overlay를 렌더링합니다.
 * 부모에 'position: relative와 overflow: hidden'이 필요합니다.
 *
 * 내용은 조정을 편안히 하기 위해 직접 칠드런으로 받고
 * 기본 영역은 15px, 버튼영역은 아래로 28px, 위로 18px의 패딩값을 기본적으로 가집니다.
 *
 * 버튼의 텍스트 굵기나 세부 조정이 필요할 경우 primaryButtonClassname에 추가 입력해주세요
 * onConfirm, onCancel에 버튼 작동시 필요한 기능을 넣어주시고
 * 정의되지 않을 시 기본적으로 onClose가 작동합니다 (배경 및 esc이벤트에도 마찬가지)
 *
 * 모달이 열려 있는 동안 기본 컨텐츠 스크롤이 잠기고 여러 모달 중첩시 마지막으로 닫힌 모달이
 * scroll lock을 해제합니다.
 */
export default function Modal({
  isOpen,
  onClose,
  children,
  onConfirm,
  onCancel,
  closeOnOverlay = true,
  buttonVariant = 'single',
  primaryButtonClassname = '',
  confirmText = '확인',
  cancelText = '취소',
  isHighlightButton = false,
}: ModalProps) {
  // ESC key + scroll lock
  useEffect(() =&amp;gt; {
    if (!isOpen) return;
    const handler = (e: KeyboardEvent) =&amp;gt; {
      if (e.key === 'Escape') onClose?.();
    };
    window.addEventListener('keydown', handler);
    document.body.style.overflow = 'hidden';
    return () =&amp;gt; {
      window.removeEventListener('keydown', handler);
      document.body.style.overflow = '';
    };
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    &amp;lt;div
      className=&quot;absolute inset-0 z-1000 flex animate-overlay-in items-center justify-center bg-black/40 p-4&quot;
      onClick={(e) =&amp;gt; {
        if (closeOnOverlay &amp;amp;&amp;amp; e.target === e.currentTarget) onClose?.();
      }}
    &amp;gt;
      &amp;lt;div
        className={`max-h-138.25 w-full min-w-75 animate-card-in rounded-[15px] bg-white p-3.75 shadow-[0_2px_13px_-5px_rgba(0,0,0,0.25)] sm:max-h-140`}
        onClick={(e) =&amp;gt; e.stopPropagation()}
      &amp;gt;
        {children &amp;amp;&amp;amp; (
          &amp;lt;div
            className={cn(
              'wrap-break-word max-h-79.5 overflow-y-scroll sm:max-w-auto',
              '[&amp;amp;::-webkit-scrollbar]:w-1.75',
              '[&amp;amp;::-webkit-scrollbar-thumb]:rounded-xs',
              '[&amp;amp;::-webkit-scrollbar-thumb]:bg-main-green',
              '[&amp;amp;::-webkit-scrollbar-track]:bg-transparent',
            )}
          &amp;gt;
            {children}
          &amp;lt;/div&amp;gt;
        )}

        {buttonVariant !== 'none' &amp;amp;&amp;amp; (
          &amp;lt;div className=&quot;flex gap-2.5 pt-4.5 pb-7&quot;&amp;gt;
            {(buttonVariant === 'double' ||
              buttonVariant === 'AsymmetricDouble') &amp;amp;&amp;amp; (
              &amp;lt;ModalButton
                variant=&quot;secondary&quot;
                onClick={onCancel ?? onClose}
                className={cn(
                  buttonVariant === 'AsymmetricDouble' ? 'flex-2' : 'flex-3',
                )}
              &amp;gt;
                {cancelText}
              &amp;lt;/ModalButton&amp;gt;
            )}
            &amp;lt;ModalButton
              variant={isHighlightButton ? 'highlight' : 'primary'}
              onClick={onConfirm ?? onClose}
              className={cn(
                buttonVariant === 'single' ? 'mx-18.75' : 'flex-3',
                primaryButtonClassname,
              )}
            &amp;gt;
              {confirmText}
            &amp;lt;/ModalButton&amp;gt;
          &amp;lt;/div&amp;gt;
        )}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/reference/react-dom/createPortal&quot;&gt;react createPortal 자료&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev Study/WEB</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/62</guid>
      <comments>https://brandofme.tistory.com/entry/Nextjs-tailwindCSS%EB%A1%9C-%EB%AA%A8%EB%8B%AC%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry62comment</comments>
      <pubDate>Mon, 6 Apr 2026 00:58:16 +0900</pubDate>
    </item>
    <item>
      <title>디지털 하나로 8기 4개월 후기</title>
      <link>https://brandofme.tistory.com/entry/%EB%94%94%EC%A7%80%ED%84%B8-%ED%95%98%EB%82%98%EB%A1%9C-8%EA%B8%B0-4%EA%B0%9C%EC%9B%94-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;6개월 과정 중 벌써 두 달 남았다.&lt;br /&gt;벌써 프론트 강의가 끝나고 스프링 교육중이라는 게 믿겨지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 들어왔을 때가 한달 전같은데 시간이 엄청 빠르게 흘러간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 초심으로 돌아가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초심을 다시 정리해보면,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 부트 캠프에 온 이유는&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 풀스택 개발을 조금 더 공부해보고 싶어서였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 리엑트/넥스트를 다뤄본 적은 있지만 스프링 등 백엔드는 거의 하지 않았고, 그 이전에는 ios를 했기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹 쪽 공부를 더 해보고 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 금융권 개발은 어떤 분위긴지 알아보고 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 은행에서 하는 부트캠프가 궁금했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4개월 간 나는 이런 활동들을 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csfGt3/dJMcafyYpZZ/AgAJ7tJr2Y1tXJ3ZPV7ynK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csfGt3/dJMcafyYpZZ/AgAJ7tJr2Y1tXJ3ZPV7ynK/img.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;300&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csfGt3/dJMcafyYpZZ/AgAJ7tJr2Y1tXJ3ZPV7ynK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsfGt3%2FdJMcafyYpZZ%2FAgAJ7tJr2Y1tXJ3ZPV7ynK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BXb98/dJMcabXCJAI/yJnDD17BixiEmURZ53A2x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BXb98/dJMcabXCJAI/yJnDD17BixiEmURZ53A2x1/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot; data-filename=&quot;blob&quot; width=&quot;300&quot; height=&quot;225&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BXb98/dJMcabXCJAI/yJnDD17BixiEmURZ53A2x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBXb98%2FdJMcabXCJAI%2FyJnDD17BixiEmURZ53A2x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 입학식은 하나은행에서 있었는데 사옥이 너무 예쁘고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크고 깔끔하고 복지가 잘 되어있는 게 보여서 은행... 너무 좋은데 라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시에 부트캠프인데도 불구하고 후디에 텀블러까지 알차게 챙겨준 기프트 박스도 넘 좋았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFKzW4/dJMcagEEFa7/CA7HnqJYQkC6gdTYhvPyo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFKzW4/dJMcagEEFa7/CA7HnqJYQkC6gdTYhvPyo0/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;400&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFKzW4/dJMcagEEFa7/CA7HnqJYQkC6gdTYhvPyo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFKzW4%2FdJMcagEEFa7%2FCA7HnqJYQkC6gdTYhvPyo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfnLNQ/dJMcabXCJAK/i7C9Fw3ihrG9iBqiDfVtVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfnLNQ/dJMcabXCJAK/i7C9Fw3ihrG9iBqiDfVtVk/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;400&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfnLNQ/dJMcabXCJAK/i7C9Fw3ihrG9iBqiDfVtVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfnLNQ%2FdJMcabXCJAK%2Fi7C9Fw3ihrG9iBqiDfVtVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의실 로비에 있던 크리스마스 트리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연말 분위기도 내주고 크리스마스 즈음에 사탕찾기 같은 이벤트도 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 친절하게 살펴주시는 매니저님과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커피머신이 있는 로비가 있어서 틈날 때마다 쉴 수 있었던 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djau6p/dJMcaaRYr70/TZzJFjlsGU2IL3GuV5z040/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djau6p/dJMcaaRYr70/TZzJFjlsGU2IL3GuV5z040/img.jpg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djau6p/dJMcaaRYr70/TZzJFjlsGU2IL3GuV5z040/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjau6p%2FdJMcaaRYr70%2FTZzJFjlsGU2IL3GuV5z040%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4032&quot; height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqXU8K/dJMcabXCJAG/5Xyx1TkVTl5ux53BXoFtpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqXU8K/dJMcabXCJAG/5Xyx1TkVTl5ux53BXoFtpk/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqXU8K/dJMcabXCJAG/5Xyx1TkVTl5ux53BXoFtpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqXU8K%2FdJMcabXCJAG%2F5Xyx1TkVTl5ux53BXoFtpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의실&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 발표도 하고 앉아서 충전하면서 노트북 뚜들기고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 중간중간 조용조용 조심하면서 칠판에 그리면서 회의하고 그랬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 칠판 덕분에 바로바로 소통이 가능해서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식사는... 첫 몇 달. 거의 3개월 가량을 밥플러스라는 한식 뷔페에서 먹었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JhZ5S/dJMcabXCJAJ/i6B0Hz9xj1wsf3viyIdllk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JhZ5S/dJMcabXCJAJ/i6B0Hz9xj1wsf3viyIdllk/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 67.8645%; margin-right: 10px;&quot; data-widthpercent=&quot;68.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JhZ5S/dJMcabXCJAJ/i6B0Hz9xj1wsf3viyIdllk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJhZ5S%2FdJMcabXCJAJ%2Fi6B0Hz9xj1wsf3viyIdllk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beTY7A/dJMcafyYpZX/KEgcIOLFV4O4JjQsNkKFh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beTY7A/dJMcafyYpZX/KEgcIOLFV4O4JjQsNkKFh0/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;493&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;31.34&quot; data-filename=&quot;blob&quot; style=&quot;width: 30.9727%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beTY7A/dJMcafyYpZX/KEgcIOLFV4O4JjQsNkKFh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeTY7A%2FdJMcafyYpZX%2FKEgcIOLFV4O4JjQsNkKFh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴가 올라오는 카톡 계정은 여전히 팔로우되어있다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 3달가량 먹다보니 물려서 중간중간 다른 데도 가다가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Gcdj/dJMcagEEFa6/tYPqyyKG9Ovq0rGNJIuQKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Gcdj/dJMcagEEFa6/tYPqyyKG9Ovq0rGNJIuQKk/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 63.2558%; margin-right: 10px;&quot; data-widthpercent=&quot;64&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Gcdj/dJMcagEEFa6/tYPqyyKG9Ovq0rGNJIuQKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Gcdj%2FdJMcagEEFa6%2FtYPqyyKG9Ovq0rGNJIuQKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pKrQW/dJMcafFI7D2/pwdKEjkY86SPro4G3oedA0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pKrQW/dJMcafFI7D2/pwdKEjkY86SPro4G3oedA0/img.jpg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;36&quot; style=&quot;width: 35.5814%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pKrQW/dJMcafFI7D2/pwdKEjkY86SPro4G3oedA0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpKrQW%2FdJMcafFI7D2%2FpwdKEjkY86SPro4G3oedA0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 차라리 편의점에서 많이 먹는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육장 자체는 엄청 크고 2호선 라인이라 교통 편리하고 좋은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성수라 그런지 물가가 사악하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간중간 저렴한 칼국수 집이나 가게들을 찾아서 버티는 중... !&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 맛집도 가고 싶은데... 흠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 가게 안되고 맨날 편의점이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kz4jH/dJMcafyYpZ0/vnp0I64TJSb1bkJZBwUMPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kz4jH/dJMcafyYpZ0/vnp0I64TJSb1bkJZBwUMPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kz4jH/dJMcafyYpZ0/vnp0I64TJSb1bkJZBwUMPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkz4jH%2FdJMcafyYpZ0%2Fvnp0I64TJSb1bkJZBwUMPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;400&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 하나은행 명동사옥 특강 방문했을 때 찍은 야간금고다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 시장 상인들이 새벽에만 거래가 가능했을 때 열어두는 창구였다는데 이젠 흔적만 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 기억도 아마 두 달 뒤면 흔적만 남는 게 아닐까 잠시 고민해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 풀스택 개발 공부&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이야기로 돌아가면 생각보다 개발적인 실력은 크게 늘진 못한 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 노력도 부족하긴 했지만 다 잘하는 분들 사이에서 PR을 열심히 따라가느라 엄청 노력했던 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;강의 내용도 익히 알고 있는 사람들도 많은 것 같고, 어떤 분은 진도가 느리다고 생각하고 어떤 분은 빠르다고 생각하는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 일단 뭐든지 최선을 다하는 편이라 열심히 으쌰으쌰 따라갔던 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;넥스트도 강의 들으면서 들으니 색달랐는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;일단 자바 스프링 처음 다뤄보고 있는데 재밌다! (적성에 맞음)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 자꾸 머릿속에서 js/ts/java/sql이 섞이고 섞이고 있어서 넘 웃기다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오늘도 강사님이 질문하셨는데 bigDecimal 대응하는 sql 타입이 뭐냐고 물어서, Double...? 아니지, 어..... Long 이랬다 씁...&lt;br /&gt;정답은 Decimal ㅎㅎㅎㅎ...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 금융권 개발 분위기 익히기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;금융 기반 프로젝트를 하면서 기술적 가능성 검토를 엄청 열심히 하게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가능과 불가능의 영역, 합법과 탈세의 영역을 고민하면서 은행의 수익구조 공부도 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각할 게 생각보다 너무 많아서 사실 기술보다는 진짜 돼? 이게 진짜 돼? 공부에 + API 사용 가능하니? + 외부에 일부러 노출 안하는 거 아니니? 공부에 가까웠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 특강 들으면서 금융 개발은 원래 이렇구나... 하고 생각하니까 금융권 분위기나 기초 공부에는 큰 도움이 된 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;은행 현직자 특강들을 들으면서 금융권과 개발이라는 도메인에 대해 생각해볼 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 최근에 한 특강에서 개발이 아닌 기획자분의 말씀이 오히려 크게 다가오기도 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 금융권 기획자는 거의 개발자 수준으로 알고 있는 것 같았다. 그분이 하는 말의 절반정도만 이해할 수 있었다.. .ㅎㅎ;;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오히려 금융권 개발자분은 열심히 금융에 대한 이야기를 풀고 가셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;금융 프로젝트를 진행하면서 생각보다 개발보다는 금융 자체 지식 + 법적으로 되는가의 영역들을 고민했던 터라 현직자분들의 말이 더 공감되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런 이야기들을 들으면서 도메인 지식이란 무엇일까 다시 생각하게 되었고, T자형 인재상에 대해 다시 생각했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 기타 늘은 거&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ai 통한 영상 만들기 (프롬프팅)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;노트북 화면 촬영하기 영상만들기...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트하면서 처음으로 다빈치 리졸브를 다뤄보았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전에 프리미어나 에펙은 쪼오끔 찍먹한 적은 있는데 다빈치는 완전 처음이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그치만 돈이 없고, 필요한 기능은 많지 않아서 일단 우다닥 했는데 간단한 컷편집과 배속, dB 조정 정도는 가능하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이야 만류 귀종이라더니 일단 프리미어 배워뒀던 게 큰 도움이 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 말로만 듣던 jitter를 처음 써봤는데 모션 템플릿이 있어서 도움이 되긴 했다. 그렇지만 세부 디테일은 노가다...여서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아 어도비가 진짜 앱을 잘만들었구나 하고 다시 느꼈다. (그치만 가격이 사악하죠)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근의 고민&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-29 오전 9.21.25.png&quot; data-origin-width=&quot;3014&quot; data-origin-height=&quot;1632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IVlNM/dJMcafeGSBo/3agnLKvoC0tmpJI8SeU101/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IVlNM/dJMcafeGSBo/3agnLKvoC0tmpJI8SeU101/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IVlNM/dJMcafeGSBo/3agnLKvoC0tmpJI8SeU101/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIVlNM%2FdJMcafeGSBo%2F3agnLKvoC0tmpJI8SeU101%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3014&quot; height=&quot;1632&quot; data-filename=&quot;스크린샷 2026-01-29 오전 9.21.25.png&quot; data-origin-width=&quot;3014&quot; data-origin-height=&quot;1632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 AI의 발전을 보면서 현타를 많이 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 ai 이미지인데 사실 영상의 일부다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데모 영상을 만들 때나 디자인 할 때도 피그마 ai나 ai 영상 제작을 쓰면 훨씬 빠르게 퀄리티 있게 나오는 것처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드들도 중간중간 그래서 ... 뭔가 약간의 슬픔을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 나는 언젠가 전체를 조망하는 기술적 지식 자체도 다시 주목받을 거라 생각하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기초에 충실한 강사님의 강의가 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 ai 부트 캠프가 많은 요즘에 이런 이론 + 실전 중심 개발 이야기를 들을 수 있는 곳이 얼마 없어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 만족하면서 듣고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dndOx8/dJMcahwMUte/mtkfS8IP9N22WgpcOlK9Uk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dndOx8/dJMcahwMUte/mtkfS8IP9N22WgpcOlK9Uk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dndOx8/dJMcahwMUte/mtkfS8IP9N22WgpcOlK9Uk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdndOx8%2FdJMcahwMUte%2FmtkfS8IP9N22WgpcOlK9Uk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;400&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 마지막이 기대 + 불안하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대체 어떤 일들이 펼쳐질까...!?&lt;/p&gt;</description>
      <category>Dev Study/비전공자 개발일지</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/61</guid>
      <comments>https://brandofme.tistory.com/entry/%EB%94%94%EC%A7%80%ED%84%B8-%ED%95%98%EB%82%98%EB%A1%9C-8%EA%B8%B0-4%EA%B0%9C%EC%9B%94-%ED%9B%84%EA%B8%B0#entry61comment</comments>
      <pubDate>Tue, 3 Mar 2026 19:46:16 +0900</pubDate>
    </item>
    <item>
      <title>Spring MVC, Swift MVC, MVVM 헷갈리는 부분 정리</title>
      <link>https://brandofme.tistory.com/entry/Spring-MVC-Swift-MVC-MVVM-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;사담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 swift를 먼저 접한 타입이라서 spring의 어노테이션 개념이 익숙한듯 낯선 것 같다.&lt;br /&gt;그러다 mvc개념을 접했는데 머릿속이 조금 복잡해져서 한 번 정리하고 넘어가고자 한다.&lt;/p&gt;
&lt;h1&gt;swift&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;swift의 MVC&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view: UI 표현 (UIKit 기준 스토리보드 연결)&lt;/li&gt;
&lt;li&gt;viewController: 이벤트 처리 + 상태 관리 + 네비게이션 + 유저액션&lt;/li&gt;
&lt;li&gt;Model: 데이터 구조 및 데이터 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 리퀘스트하면 컨트롤러가 모델에 정보를 요청, 받아서 뷰에 데이터 전달, 뷰가 응답함&lt;br /&gt;뷰 컨트롤러가 책임량이 과다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;swift의 MVVM&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view: UI 표현 + 입력 처리&lt;/li&gt;
&lt;li&gt;model: 데이터&lt;/li&gt;
&lt;li&gt;viewModel: 상태 + UI 변환 로직 &lt;br /&gt;(유저 액션은 보통 view에서 viewModel로 전달, 상태변화, 비즈니스 로직으로 해석)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view가 viewModel을 관찰하며 UI 업데이트&lt;br /&gt;viewModel에서 가지고 있는 데이터를 view에서 관찰, 변경 시 view에 적용&lt;br /&gt;뷰컨트롤러의 책임이 줄어듦&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;swiftUI + swiftData 소규모 프로젝트 (거의 MV)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model: 데이터와 변경 처리 일부 관리&lt;br /&gt;view: (view에서 binding된 데이터의 이벤트 확인) =&amp;gt; 변경 시 바로 적용&lt;br /&gt;이벤트와 바인딩 처리를 단순화하는 프레임워크를 통하여 거의 모든 이벤트가 별도의 컨트롤러나 viewModel 선언없이&lt;br /&gt;이루어짐&lt;br /&gt;그러나 내부적인 구조를 생각하면 MVVM의 이벤트 리스너와 큰 차이가 없을 것 같기도 하다.&lt;/p&gt;
&lt;h1&gt;spring&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;spring의 MVC&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/images/mvc.png&quot; alt=&quot;Spring Web MVC의 요청 처리 워크플로&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view template: UI (view template)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSP, Velocity 등등의 프로엠워크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;model: 뷰에 전달할 데이터를 담는 객체&lt;/li&gt;
&lt;li&gt;front controller: 클라이언트 요청 처리 (dispatcher servlet)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade pattern (서브 시스템 단순 창구 제공)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;controller: 사용자 입력 해석, 모델 생성, 비즈니스 로직 수행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 레이어에서 비즈니스 로직 처리, DBMS와 연결된 DAO에서 데이터 처리&lt;/li&gt;
&lt;li&gt;dispatcher servlet이 request를 받아 경로 등에 맞게 세부 컨트롤러들에 핸들링,&lt;/li&gt;
&lt;li&gt;모델을 받아 응답도 dispatcher가 수행함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;헷갈린 점 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;spring의 서비스 레이어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 swift프로젝트를 한 적이 없어서 그런지 서비스 레이어와 dao의 엄격한 구분 등이 낯설게 다가왔다.&lt;br /&gt;그게 mvc에 종속된 개념인 줄 알았으나 아니었다. (해당 내용은 계층형 아키텍처 개념이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mvc자체는 swift의 mvc와 동일한 패턴이되 swift에서 mvc와 mvvm을 나눈 것처럼&lt;br /&gt;화면의 상태관리를 누가 처리할 것이냐가 중요한 패턴이라기보단 spring의 경우 들어온 request를 어떻게 분배하고 처리할 것인가,&lt;br /&gt;즉 dispatch servlet의 역할이 더 중요한 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론적 MVC 구조는 동일하지만 적용 맥락이 다른 느낌이다.&lt;br /&gt;IOS에서는 클라이언트의 UI를 고려한 아키텍처 느낌이라면 Spring에서는 서버 사이드 요청, 응답 중심의 아키텍처 느낌이 강하다.&lt;br /&gt;view의 상태관리가 크게 중요하지 않은 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift MVC에서의 ViewController는 &quot;UI 이벤트 루프 안에서 상태를 관리하는 객체&quot;라면&lt;br /&gt;반면 Spring MVC의 Controller는 &quot;HTTP 요청 단위로 동작하는 stateless 컴포넌트&quot;에 가깝다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://preview.redd.it/swiftui-reactive-clean-architecture-using-mvvm-with-unit-v0-llbnwcun4qjd1.jpeg?width=1080&amp;amp;crop=smart&amp;amp;auto=webp&amp;amp;s=e4c56989f3f91c9869ab605edb65ae6ca65b89f5&quot; alt=&quot;reddit swiftUI enterprize level template&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 mvvm 혹은 mv패턴 양쪽 모두 대규모 서비스가 될 경우 useCase 등의 레이어분리가 나중에 따른다.&lt;br /&gt;또한 스프링의 DI 처럼 의존성 주입 관련 서드파티 프레임워크 (Needle, Swinject)를 사용하기도 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유사하다고 생각한 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어노테이션을 통한 객체 활용이 조금 유사하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;swiftData의 경우 @Model, java @Entity 등 객체와 db를 매핑하고 관계 정의를 한다는 점에서 유사하다고 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 spring의 경우가 훨씬 더 적용 범위가 큰 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;SwiftData의 @Model은 스위프트의 매크로 시스템 기반으로 컴파일 시 변경 감지 코드가 삽입되고,&lt;br /&gt;저장 메타데이터가 생성되고, 관찰 가능한 타입으로 확장되어 실행 전에 구조가 확정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring JPA 기준 어노테이션은 컴파일 시 변화 없이 실행 시 JPA 프로바이더가 리플렉션으로 클래스 구조를 읽고&lt;br /&gt;프록시 클래스를 동적으로 생성하여 객체를 감싼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체 왜일까? 하고 고민이 많이 된다. 아무리 생각해도 proxy로 감싸 처리할 경우에 에러가 늘어날 것 같은데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 찾아본 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 등을 고려할 때 DB의 호출 시점을 통제하거나 한번에 들어오는 양을 조절하거나&lt;br /&gt;핵심로직에 부가 기능을 분리 삽입하는 느낌으로 함수 자체를 가로챌 수 없는 java를 보완하기 위함인 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 swift의 매크로는 컴파일 처리, Spring도 요즘은 AOT처리로 컴파일 시점에 미리 준비하기도 함)&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;Caller &amp;rarr;
   Proxy &amp;rarr;
      (부가 기능 수행) &amp;rarr;
         Target Method &amp;rarr;
      (후처리) &amp;rarr;
Return&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1771840878862&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;22.&amp;nbsp;Web MVC framework&quot; data-og-description=&quot;All MVC frameworks for web applications provide a way to address views. Spring provides view resolvers, which enable you to render models in a browser without tying you to a specific view technology. Out of the box, Spring enables you to use JSPs, Velocity&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/mvc.html&quot; data-og-url=&quot;https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/mvc.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OkUAH/dJMb9cBEZ9D/KOeYXWqE1QNHiWFH9lcnN1/img.png?width=800&amp;amp;height=513&amp;amp;face=0_0_800_513,https://scrap.kakaocdn.net/dn/ff4Lt/dJMb9dHkWcp/IQz4d1hyW4ekew5n3nu0Zk/img.png?width=582&amp;amp;height=536&amp;amp;face=0_0_582_536,https://scrap.kakaocdn.net/dn/vP0mZ/dJMb9frCmoJ/3zdPPFKTKfUeEdK00ymtaK/img.png?width=582&amp;amp;height=529&amp;amp;face=0_0_582_529&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/mvc.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/mvc.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OkUAH/dJMb9cBEZ9D/KOeYXWqE1QNHiWFH9lcnN1/img.png?width=800&amp;amp;height=513&amp;amp;face=0_0_800_513,https://scrap.kakaocdn.net/dn/ff4Lt/dJMb9dHkWcp/IQz4d1hyW4ekew5n3nu0Zk/img.png?width=582&amp;amp;height=536&amp;amp;face=0_0_582_536,https://scrap.kakaocdn.net/dn/vP0mZ/dJMb9frCmoJ/3zdPPFKTKfUeEdK00ymtaK/img.png?width=582&amp;amp;height=529&amp;amp;face=0_0_582_529');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;22.&amp;nbsp;Web MVC framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;All MVC frameworks for web applications provide a way to address views. Spring provides view resolvers, which enable you to render models in a browser without tying you to a specific view technology. Out of the box, Spring enables you to use JSPs, Velocity&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1771840894437&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Reddit의 swift 커뮤니티: SwiftUI 반응형 클린 아키텍처 MVVM 사용, 유닛 테스트 포함 - 엔터프라이즈급&quot; data-og-description=&quot;swift 커뮤니티에서 이 게시물을 비롯한 다양한 콘텐츠를 살펴보세요&quot; data-og-host=&quot;www.reddit.com&quot; data-og-source-url=&quot;https://www.reddit.com/r/swift/comments/1ewjiu7/swiftui_reactive_clean_architecture_using_mvvm/?tl=ko&quot; data-og-url=&quot;https://www.reddit.com/r/swift/comments/1ewjiu7/swiftui_reactive_clean_architecture_using_mvvm/?tl=ko&amp;amp;seeker-session=true&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o6ZvQ/dJMb8RRONja/DPcOAGTPUgNZdKpOng8AUk/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/csfaLg/dJMb8PGs4wC/FsbuOLtYPM7S9wz5g7gkB1/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/swift/comments/1ewjiu7/swiftui_reactive_clean_architecture_using_mvvm/?tl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.reddit.com/r/swift/comments/1ewjiu7/swiftui_reactive_clean_architecture_using_mvvm/?tl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o6ZvQ/dJMb8RRONja/DPcOAGTPUgNZdKpOng8AUk/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/csfaLg/dJMb8PGs4wC/FsbuOLtYPM7S9wz5g7gkB1/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Reddit의 swift 커뮤니티: SwiftUI 반응형 클린 아키텍처 MVVM 사용, 유닛 테스트 포함 - 엔터프라이즈급&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;swift 커뮤니티에서 이 게시물을 비롯한 다양한 콘텐츠를 살펴보세요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.reddit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev Study/DEV</category>
      <category>MVC</category>
      <category>MVVM</category>
      <category>spring</category>
      <category>Swift</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/60</guid>
      <comments>https://brandofme.tistory.com/entry/Spring-MVC-Swift-MVC-MVVM-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%A0%95%EB%A6%AC#entry60comment</comments>
      <pubDate>Mon, 23 Feb 2026 19:02:52 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 코테용 stdin 세팅</title>
      <link>https://brandofme.tistory.com/entry/Javascript-%EC%BD%94%ED%85%8C%EC%9A%A9-stdin-%EC%84%B8%ED%8C%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;javascript에는 대표적인 stdin(즉 키보드 인풋 등)을 받을 수 있는 모듈이 2가지 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 fs, 다른 하나는 readline이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fs&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fs는 파일을 읽는 프로그램으로 파일을 읽어들이고 처리가 가능하지만 stdin도 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);
let 사용자_입력 = fs.readFileSync(0).toString().trim();

import fs from &quot;fs&quot;
let input = fs.readFileSync(0).toString().trim();
console.log(`Your score is ${input} point.`);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fs 모듈의 readFileSync는 인자에 따라 다음과 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;번호&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;stdin (표준 입력)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;stdout (표준 출력)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;stderr (표준 에러)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 0으로 세팅하면 표준 입력을 읽어들여서 Sync 즉, 동기하게 처리해준다.&lt;br /&gt;다만 별도의 인코딩을 지정하지 않은 readFileSync로 들어오는 입력값은 string값이 아니라 Buffer값, 즉 바이너리 데이터 타입으로 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그 값을 string으로 바꾸고 그 string을 trim처리를 통해 문자열 앞 뒤 공백을 제거한다. (&quot; &quot;, \n, \t 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 구분자를 두고 여러개가 한번에 들어올 경우에는 다음과 같이 받으면 된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);
let 사용자_입력 = fs.readFileSync(0).toString().trim().split(/\s+/);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;regex로 s+는 스페이스를 비롯한 공백묶음을 뜻한다.&lt;br /&gt;이때 split 리턴값으로 배열을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 readFileSync를 원래 목표대로 파일을 읽는다고 했을 때 json파일일 경우 다음과 같이 처리도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const fs = require('fs');

// JSON 파일을 동기적으로 읽기 // 두번째 인자는 utf8 등 인코딩 형식
const data = fs.readFileSync('data.json', 'utf8');

// JSON 문자열을 객체로 변환
const jsonData = JSON.parse(data);

// JSON 데이터 처리
const keys = Object.keys(jsonData);
console.log('JSON 데이터의 키 목록:', keys);

// 특정 키의 값 출력
const key = 'name';
if (jsonData[key]) {
  console.log(`${key}: ${jsonData[key]}`);
} else {
  console.error(`키 '${key}'가 JSON 데이터에 없습니다.`);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;readline&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리드라인과 fs의 가장 큰 차이는 readline은 비동기로 입력을 받는다는 것이다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;const readline = require('readline')
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 readline을 위해 입력을 받을 별도의 연결을 활성화해야 한다.&lt;br /&gt;process.stdin은 여기서 스탠다드 인풋, 즉 키보드나 입력장치를 통한 입력을 뜻한다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const lines = [];

rl.on('line', function(line) {
  if (line.toLowerCase() === 'exit') {
    rl.close();
  } else {
    lines.push(line);
  }
}).on('close', function() {
  console.log('입력받은 줄 목록:');
  lines.forEach((line, index) =&amp;gt; {
    console.log(`${index + 1}: ${line}`);
  });
  process.exit();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 밖에서 값을 읽기 위한 객체를 생성하고 rl. 즉 오픈된 연결안에서 한줄 단위로 입력을 받아들일 때마다 실행되며, on 'close'를 세팅하면 입력이 종료되었을 때 실행된다.&lt;br /&gt;참고로 말하자면 이건 비동기 실행이기 때문에 처리 시점을 확실히 하기 위해서는 close안에서 콜백으로 처리를 해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백으로 처리를 하지 않고 외부에서 lines를 접근하려고 하면 undefined, 혹은 초기값인 []가 먼저 떨어진다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100.698%; height: 152px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;fs 모듈&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;readline 모듈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;입력 소스&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;소스 파일&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;시스템 표준 입력(콘솔)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;사용&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;파일에서 데이터 읽기&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;라인 단위 스트리밍 입력 전반에 적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;동기/비동기&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;동기(readFileSync), 비동기 가능(readFile)&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;비동기적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;파일 크기에 따라 다름&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;실시간 입력 처리에 적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;복잡도&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;상대적으로 간단한 설정&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;이벤트 리스너 설정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;파일 읽기 쓰기, 대용량 파일 처리&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;실시간 입력 및 다중 라인 입력 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 13.6259%; height: 19px;&quot;&gt;사용 예제&lt;/td&gt;
&lt;td style=&quot;width: 38.9746%; height: 19px;&quot;&gt;로그 파일 분석, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;설정 파일 읽기 등&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.0971%; height: 19px;&quot;&gt;CLI 인터페이스, 사용자 입력 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정리하면 위와 같다. fs는 파일에서 데이터를 입력받기 위해 주로 쓰인다.&lt;br /&gt;readline은 주로 비동기로, 파일보다는 사용자의 입력을 이벤트 처리할 때 주로 사용된다.&lt;br /&gt;readline은 입력을 한줄단위로 처리하기 때문에 불필요하게 모든 데이터를 메모리에 잡아둘 필요없이 즉시 실행도 가능하고, 이벤트 루프로 처리되어 다른 프로세스를 방해하지 않는다는 장점이 있으나 그만큼 다루기 불편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 걸 비동기로 처리하기 위해서 콜백으로 처리하거나 별도의 async로 싸서 await을 걸어줘야 한다.&lt;br /&gt;또한 작은 단위의 파일을 읽을 때는 readFileSync의 경우 이벤트 리스너 등록도 필요없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 알고리즘과 같이 입력 크기가 작고, 동기 I/O가 문제가 되지 않는 경우에는 fs의 readFileSync를 쓰는 게 훨씬 더 편리하다.&lt;/p&gt;</description>
      <category>Dev Study/비전공자 개발일지</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/59</guid>
      <comments>https://brandofme.tistory.com/entry/Javascript-%EC%BD%94%ED%85%8C%EC%9A%A9-stdin-%EC%84%B8%ED%8C%85#entry59comment</comments>
      <pubDate>Mon, 29 Dec 2025 20:16:48 +0900</pubDate>
    </item>
    <item>
      <title>유튜브 iframe 사용 중 CORS 에러 + Next.js CORS</title>
      <link>https://brandofme.tistory.com/entry/%EC%9C%A0%ED%8A%9C%EB%B8%8C-iframe-%EC%82%AC%EC%9A%A9-%EC%A4%91-CORS-%EC%97%90%EB%9F%AC-Nextjs-CORS</link>
      <description>&lt;h1&gt;Error Message&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;Access to fetch at &amp;#39;https://googleads.g.doubleclick.net/pagead/viewthroughconversion/962985656/..6ftNvA&amp;amp;label=followon view&amp;amp;ptype=no rmkt&amp;amp;random=1073074690&amp;amp;cv attributed=0&amp;#39; (redirected from https://www.youtube.com/pagead/viewthroughconversion/962985656/?backend=inn..6GDrDmvJEZrnipc06ftNvA&amp;amp;label=followon view&amp;amp;ptype=no rmkt&amp;amp;random=1073074690&amp;#39;) from origin
https://www.youtube.com&amp;#39; has been blocked by CORS policy: No &amp;#39;Access-Control-Allow-Origin&amp;#39; header is present on the requested resource.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;처음에 이 메세지를 만났을 때 엄청 당황했다.&lt;br&gt;엥? 내가 ? CORS를 위반했다고?&lt;br&gt;나는 CORS에 걸릴만한 짓을 한 게 없다....!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-18 오후 7.43.35.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blomDT/dJMcac2IOmp/HgYO1dNFPu24QP3jCiKYBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blomDT/dJMcac2IOmp/HgYO1dNFPu24QP3jCiKYBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blomDT/dJMcac2IOmp/HgYO1dNFPu24QP3jCiKYBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblomDT%2FdJMcac2IOmp%2FHgYO1dNFPu24QP3jCiKYBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1742&quot; height=&quot;1152&quot; data-filename=&quot;스크린샷 2025-12-18 오후 7.43.35.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;1152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;네트워크에서 해당 요청을 조금 더 자세히 본 결과 request Headers를 보면 요청 url이 내 사이트가 아닌 것을 확인가능하다.&lt;/p&gt;
&lt;p&gt;정확히는 authority부분을 보면 googleads.g.doubleclick.net이라고 나온다.&lt;/p&gt;
&lt;p&gt;authority는 HTTP/2에서 Host 헤더를 대체하는 pseudo-header로, 요청 대상 서버의 호스트 정보를 나타낸다.&lt;/p&gt;
&lt;p&gt;googleads.g.doublclick.net은 Google Ads / DoubleClick 광고 및 전환 추적용 도메인이름에서부터 알다싶이 구글 광고, 마케팅 관련 사이트이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;message ClientVariations {
  // Active Google-visible variation IDs on this client. These are reported for analysis, but do not directly affect any server-side behavior.
  repeated int32 variation_id = [3300100, 3300134, 3313321, 3323242, 3330196, 3362822];
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;쭉 내려 리퀘스트 메세지까지 보게 된다면 이런 내용이 적혀있다.&lt;br&gt;anaylysis 용도이고 서버 사이드 행위에 영향을 주지 않는다는 멘트가 나온다.&lt;/p&gt;
&lt;p&gt;또한 여기서 variation_id는 구글 내부 실험(필드 트라이얼 / A/B 테스트) 또는 기능 플래그의 고유 식별자(ID)이다.&lt;/p&gt;
&lt;p&gt;알고 보니 이 요청은 &lt;strong&gt;YouTube iframe을 삽입했을 때 내부적으로 발생하는 광고 전환 추적 요청&lt;/strong&gt;이었다.&lt;/p&gt;
&lt;p&gt;googleads.g.doubleclick.net에서 &amp;quot;&lt;a href=&quot;http://www.youtube.com&amp;quot;%EC%AA%BD%EC%9D%84&quot;&gt;www.youtube.com&amp;quot;쪽을&lt;/a&gt; Access-Control-Allow-Origin&amp;#39; header에 세팅해줘야 하는 문제였다.&lt;/p&gt;
&lt;p&gt;즉, 무시하면 된다. 해결방안 없고 내 쪽에서 만든 에러도 아니다.&lt;br&gt;우리쪽 서버는 무사하고, 기능상 에러도 없는 편이다.&lt;br&gt;5년전 스택오버플로우 게시글에도 나올 정도의 유구한 에러이지만 구글에서 해결을 안하는 에러인 것 같다.&lt;/p&gt;
&lt;p&gt;그 와중에 덩달아 공부 다시한 개념들을 정리해둔다.&lt;/p&gt;
&lt;h1&gt;CORS란?&lt;/h1&gt;
&lt;p&gt;CORS란 Cross-Origin Resource Sharing의 약자로 브라우저가 서로 다른 출처(origin) 간의 요청에 대해 응답을 JavaScript에서 읽을 수 있을지를 판단하는 보안 정책이다.&lt;/p&gt;
&lt;p&gt;CORS는 이를 제어하기 위해 HTTP 응답 헤더를 사용한다.&lt;/p&gt;
&lt;p&gt;CORS 는 교차 출처 리소스를 호스팅하는 서버가 실제 요청을 허가할 것인지 확인하기 위해 브라우저가 보내는 &amp;quot;사전 요청(프리플라이트, Preflight)&amp;quot;을 통해 요청을 보내도 되는지 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-18 오후 5.43.26.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQsS0/dJMcafyp1ye/aPqWH0ax5wT9NDGPqIdSU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQsS0/dJMcafyp1ye/aPqWH0ax5wT9NDGPqIdSU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQsS0/dJMcafyp1ye/aPqWH0ax5wT9NDGPqIdSU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQsS0%2FdJMcafyp1ye%2FaPqWH0ax5wT9NDGPqIdSU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;1180&quot; data-filename=&quot;스크린샷 2025-12-18 오후 5.43.26.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이슈 내용을 자세히 보면, preflight request라는 키워드가 보인다.&lt;/p&gt;
&lt;p&gt;특정 HTTP 요청들을 제외하면 preflight 요청으로 응답을 받을 수 있을지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://mdn.github.io/shared-assets/images/diagrams/http/cors/fetching-page-cors.svg&quot; alt=&quot;MDN-WEBDOC-CORS&quot;&gt;&lt;/p&gt;
&lt;p&gt;브라우저는 서버가 실제 해당 요청을 허가할 것인지 확인하기 위한 사전요청(preflight)를 전송하고 그에 돌려받는 응답 헤더의 정보를 확인한다. 만약 돌려받는 응답 헤더에 원하는 내용이 없으면 CORS에 위배된 것으로 보아 허용하지 않고 에러를 낸다.&lt;/p&gt;
&lt;p&gt;자세한 내용은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://mdn.github.io/shared-assets/images/diagrams/http/cors/preflight-correct.svg&quot; alt=&quot;MDN-CORS&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Client에서는 Server에서 데이터를 받아오기 전에 OPTIONS라고 어떤 요청을 보낼 수 있는지 확인하는 요청을 먼저 보낸다.&lt;/h2&gt;
&lt;p&gt;origin은 요청하고자 하는 호스트이고, request method는 post이고, request에 보낼 헤더 타입은 사용자 정의형 X-PINGOTHER와 Content-type이다.&lt;/p&gt;
&lt;p&gt;서버에서는 해당 사항을 확인하고, Access-Control-Allow-Origin에 허용되는 요청 origin을 밝힌다. *로 되어있는 경우 누구나 요청가능하다는 와일드카드이지만 권장되지 않는 사항이다. allow methods는 요청가능한 명령들을, 헤더에 담아야 하는 내용들을 다시 담고, 추가적 내용없이 해당 preflight사항을 캐시할 수 있는 시간을 제공한다. 기본 값은 5초, 최대 캐시 시간은 86400초(= 24시간)이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;
const url = &amp;quot;[https://bar.other/resources/credentialed-content/](https://bar.other/resources/credentialed-content/)&amp;quot;;  

const request = new Request(url, { credentials: &amp;quot;include&amp;quot; });  

const fetchPromise = fetch(request);  
fetchPromise.then((response) =&amp;gt; console.log(response));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또한 이렇게 credentials: include옵션을 통해 CORS 요청시에 쿠키 등에 저장된 인증 정보를 같이 담아 보낼 수도 있다.&lt;/p&gt;
&lt;p&gt;받아주는 서버는 Access-Control-Allow-Credentials: true 를 명시해야 한다&lt;/p&gt;
&lt;p&gt;또한 자격증명을 사용할 경우에는 절대 allow origin이 *와일드 카드여선 안된다.&lt;/p&gt;
&lt;p&gt;브라우저가 보내는 preflight 요청에는 쿠키나 Authorization 같은 자격 증명을 의도적으로 포함하지 않는다. 사전 요청에 대한 응답은 실제 요청이 자격 증명과 함께 수행될 수 있음을 나타내기 위해 Access-Control-Allow-Credentials: true 를 명시해야 한다. &lt;/p&gt;
&lt;h2&gt;Next.js에서의 CORS&lt;/h2&gt;
&lt;p&gt;next.js에서 서버 컴포넌트를 위주로 사용한 나는 CORS를 크게 신경쓰지 않았다. 왜냐하면 CORS 검증 자체는 브라우저에서 일어나는 일이고, server를 통한 데이터 연결에 있어서는 문제가 없었기 때문이다. &lt;/p&gt;
&lt;p&gt;브라우저에서 fetch가 이루어져 일어나는 대부분의 cors는 이를 서버 액션으로 전환하거나 route를 통해 서버에서 직접 요청을 보내도록 하면 해결된다.&lt;/p&gt;
&lt;h3&gt;참고자료&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/64973469/no-access-control-allow-origin-error-while-embeding-youtube-video&quot;&gt;Stackoverflow - No &amp;#39;Access-Control-Allow-Origin&amp;#39; error while embeding youtube video&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CORS&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CORS&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev Study/WEB</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/58</guid>
      <comments>https://brandofme.tistory.com/entry/%EC%9C%A0%ED%8A%9C%EB%B8%8C-iframe-%EC%82%AC%EC%9A%A9-%EC%A4%91-CORS-%EC%97%90%EB%9F%AC-Nextjs-CORS#entry58comment</comments>
      <pubDate>Mon, 22 Dec 2025 20:37:02 +0900</pubDate>
    </item>
    <item>
      <title>초심자를 위한 React2Shell 보안 이슈</title>
      <link>https://brandofme.tistory.com/entry/%EC%B4%88%EC%8B%AC%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-React2Shell-%EB%B3%B4%EC%95%88-%EC%9D%B4%EC%8A%88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅은 오직 교육적인 목적과 개인 학습 관련 사항을 정리하는 목적으로 작성되었으며 최신 공격 기술의 실제 구현이나 접근 경로에 대한 구체적인 코드 내용을 담고 있지 않습니다. 특정 보안에 취약해질 사항들을 직접 구현할 수 있도록 설명하지 않을 예정이며 따라서 포스팅이 모호해도 양해부탁드립니다. 또한 저 또한 초심자이기에 초심자가 이해할 수 있도록 정리하는 것을 목표로 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;문제 버전 및 해결 방안&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 보안 이슈는 react의 rsc, 리엑트 서버 컴포넌트와 서버 엑션을 사용하는 곳에서 발생합니다.&lt;br /&gt;다음 버전의 리엑트, 리엑트 dom, next.js와 같은 리엑트의 rsc 방식을 사용하는 모든 프레임워크의 경우 공지를 참조하여 업데이트 방법을 찾기 바랍니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제 버전&lt;/th&gt;
&lt;th&gt;업데이트할 수 있는 버전&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;react-server-dom-webpack, react-server-dom-parcel,react-server-dom-turbopack 19.0, 19.1.0, 19.1.1,19.2.0&lt;/td&gt;
&lt;td&gt;19.0.1, 19.1.2, 19.2.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 15.x&lt;/td&gt;
&lt;td&gt;15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 16.x&lt;/td&gt;
&lt;td&gt;16.0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 14.3.0-canary.77 and later canary releases&lt;/td&gt;
&lt;td&gt;downgrade to next@14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 15.x canary releases&lt;/td&gt;
&lt;td&gt;15.6.0-canary.58 (for 15.x canary releases)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js 16.x canary releases&lt;/td&gt;
&lt;td&gt;16.1.0-canary.12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react의 경우에는 다음과 같이 dom과 서버, parcel, webpack 등 사용하시는 프레임워크의 버전을 최신으로 업데이트해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;npm install react@latest
npm install react-dom@latest
npm install react-server-dom-parcel@latest
npm install react-server-dom-webpack@latest
npm install @vitejs/plugin-rsc@latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 버전의 Next.js를 사용하시는 경우 새 버전으로 패키지를 재설치하거나 다음과 같은 검토 후 패치 적용시키는 명령어를 사용하길 바랍니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;npx fix-react2shell-next&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;문제를 이해하기 위해 알아야 할 개념&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 업데이트가 필요한 큰 보안 문제가 발생한 다음 궁금한 것은 다음과 같은 것일 것입니다.&lt;br /&gt;왜 이런 문제가 생겼는가? 업데이트 이후에는 과연 보안에 문제가 없을 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 보안 취약점 자체나 PoC, 재현 방법 등 직접적인 공격 기술은 다루지 않습니다.&lt;br /&gt;대신 문제를 이해하는 데 필요한 javascript의 기본과 promise의 동작방식, react의 rsc 구조와 payload에 대한&lt;br /&gt;&lt;b&gt;개념적 설명&lt;/b&gt;만 제공할 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다 상세한 기술적 내용이나 코드는 공식 문서와 공개된 레퍼런스를 통해 확인하시는 것을 권장드립니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSR과 CSR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리엑트에서는 브라우저의 DOM API를 직접 다루지 않고, 가상 DOM을 기반으로&lt;br /&gt;필요한 실제 DOM 변경만 최소화하여 적용하는 방식으로 불필요한 렌더링 연산의 부하를 줄입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리엑트는 최적화를 위해 렌더링 결과로 생성된 새 가상 DOM과 이전 가상 DOM의 렌더 트리를 비교하여 그 차이(diff)를 기반으로 실제 UI에 필요한 부분만 업데이트 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모든 화면을 계속 다시 그리거나, 중복된 레이어를 다시 쌓지 않는 최적화 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR의 경우에는 CSR과 달리 서버에서 컴포넌트를 실행하여 html을 브라우저에 전송합니다.&lt;br /&gt;SSR의 경우에는 서버에서 먼저 그리고 올라가서 빠른 렌더 속도를 갖고, SEO 최적화 등의 장점을 갖지만 html만 올라가는 것이 아니라&lt;br /&gt;자신을 구현한 javascript또한 브라우저에 전송해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면 이를 기반으로 리엑트 가상 DOM이 그려져야 하기 때문입니다.&lt;br /&gt;가상 DOM이 완성되어야 정적 화면이 아닌 상호작용이 가능한 갱신이 이루어질 수 있기 때문에 SSR에 사용된 javascript는 실행 가능한 JS 번들 형태로 전송됩니다. 이렇게 전송된 번들을 추가로 브라우저에서 실행하여 react tree와 연결하고, 상태 업데이트 동작이 가능하도록 만듭니다. 그 과정을 hydration이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 사실상 SSR방식의 클라이언트 컴포넌트는 서버에서 실행된 이후 가상 DOM을 완성하기 위해 두 번 실행되는 셈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황에서 두 번 그려지지 않고도 양쪽에서 실현가능한 React Server Component가 등장합니다.&lt;br /&gt;RSC는 서버 전용 컴포넌트를 도입해 클라이언트 JS를 줄이고, Flight payload로 클라이언트 트리에 통합하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSC는 payload를 통해 두 번 실행되지 않고도 가상돔에 렌더할 수 있게 된 서버 컴포넌트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSC Flight payload를 디코딩하는 서버 함수 엔드포인트 처리 로직이 현재 공개된 보안 이슈의 중심이었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Payload 생성과 참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSC에서 하이드레이션 과정을 대체하기 위해 클라이언트에 전달되는 직렬화된 형식의 실행결과물이 rsc에서는 &lt;b&gt;스트리밍 payload&lt;/b&gt;형식입니다.&lt;br /&gt;클라이언트의 가상 dom에서는 payload를 해석하여 ui 트리에 통합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 실행된 리엑트 엘리먼트 트리는 클라이언트에서는 실행이 아닌 참조로 전달하기 위해 직렬화가 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기의 직렬화, 비직렬화는 다음과 같은 개념으로 쓰였습니다.&lt;br /&gt;직렬화(serialize): 컴퓨터 메모리의 객체를 스트링으로 바꾸는 것&lt;br /&gt;비직렬화: 스트링을 객체로 변경하는 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;payload의 형식은 flight라고 합니다. payload를 보기 위해서는 개발자 도구 창을 열면 됩니다.&lt;br /&gt;다음은 개인적으로 만들던 next.js 프로젝트의 개발자 도구 창입니다. html 소스 내의 하단에 script가 잔뜩 쌓여 있는 걸 볼 수 있고&lt;br /&gt;해당 script를 확장해보면 이미지와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cROWU8/dJMcaacJrG9/IN2EwQUtCpGj3KLRdOmrc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cROWU8/dJMcaacJrG9/IN2EwQUtCpGj3KLRdOmrc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cROWU8/dJMcaacJrG9/IN2EwQUtCpGj3KLRdOmrc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcROWU8%2FdJMcaacJrG9%2FIN2EwQUtCpGj3KLRdOmrc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1104&quot; height=&quot;328&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 스크립트들을 parser를 통해서 해석하면 객체 형태로 정보를 볼 수 있습니다.&lt;br /&gt;파싱된 자세한 형태는 스킵하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 방식을 사용해 최적화하는 다른 프레임워크, next.js는 내부적으로 generateDynamicRSCPayload 라는 function을 통해 리엑트의 payload형식을 만들도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리엑트에서는 payload를 받아 parseModelString이라는 function을 통해 컴포넌트로 변환하고, Next.js는 컴포넌트 트리에 AppRouter 위에 ServerRoot라는 컴포넌트를 추가합니다. 여기서 RSC 페이로드 데이터를 AppRouter로 스트리밍합니다.&lt;br /&gt;payload는 궁극적으로는 react 엔진에 전달되어 해석되고 실행됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;promise의 동작 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리엑트 18부터 도입된 RSC방식에서는 payload형태의 직렬화된 데이터는 한 번에 들어오는 것이 아닌 스트리밍 방식으로 전달됩니다.&lt;br /&gt;스트리밍이라는 말은 chunk 단위로 쪼개서 들어온다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 밀려오지 않고 끊어서 전달되는 것을 통해 네트워크 부하를 낮추고 렌더링 및 실행 속도를 올릴 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면, chunk단위의 반복적인 전달은 비동기이고 따라서 promise 형태로 전달된다고 추론할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 자체 의미 그대로 promise는 언제 실현될지 알 수 없기에 then을 통해 내부의 resolve값을 전달, 실행이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 Promise들이 실행된다는 조건은 javascript의 동적 타이핑과 prototype과 연계되며 심각한 이슈가 되었습니다.&lt;br /&gt;Flight payload 역직렬화 시, 신뢰되지 않은 입력을 prototype까지 확장하면서 검증 없이 처리했기 때문입니다.&lt;br /&gt;현재 업데이트 버전에서 해당 문제는 해결된 상태입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/blog/CVE-2025-66478&quot;&gt;Next.js blog - CVE-2025-66478&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components&quot;&gt;React Blog - Critical Security Vulnerability in React Server Components&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://tonyalicea.dev/blog/understanding-react-server-components&quot;&gt;Understanding React Server Components&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev Study/WEB</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/57</guid>
      <comments>https://brandofme.tistory.com/entry/%EC%B4%88%EC%8B%AC%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-React2Shell-%EB%B3%B4%EC%95%88-%EC%9D%B4%EC%8A%88#entry57comment</comments>
      <pubDate>Tue, 9 Dec 2025 19:25:22 +0900</pubDate>
    </item>
    <item>
      <title>SSR이란?</title>
      <link>https://brandofme.tistory.com/entry/SSR%EC%9D%B4%EB%9E%80</link>
      <description>&lt;h1&gt;SSR의 정의&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Server Side Rendering의 약자로, HTML을 브라우저에서 구성하는 것이 아니라 웹 서버에서 미리 생성해 브라우저로 전달하는 방식을 말한다.&lt;br /&gt;반대로 초기 HTML이 거의 비어 있는 상태에서 브라우저의 JavaScript가 UI를 구성하는 방식을 클라이언트 사이드 렌더링(CSR) 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR과 CSR은 하나의 서비스에서도 동시에 함께 사용되는 기술이다.&lt;br /&gt;둘 중 하나만 써야 하는 것이 아니라, 페이지의 목적(SEO&amp;middot;속도&amp;middot;상호작용)에 따라 적절히 섞어 쓰는 것이 일반적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AJAX와 차이 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 블로그 포스팅에서 AJAX를 다뤘는데, AJAX를 CSR과 혼동하는 경우가 많다.&lt;br /&gt;정확히 말하면 &lt;b&gt;AJAX는 &amp;ldquo;비동기적으로 데이터를 요청해 페이지 전체를 새로고침하지 않고 화면 일부만 갱신하는 기술&amp;rdquo;&lt;/b&gt;이다.&lt;br /&gt;렌더링 모델(SSR/CSR)이 아니라 그 위에서 동작하는 데이터 로딩 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CSR에서 AJAX를 자주 쓰지만 SSR 페이지에서도 AJAX는 얼마든지 사용된다.&lt;/p&gt;
&lt;table style=&quot;height: 153px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;구분&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;SSR (Server-Side Rendering)&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;AJAX (비동기 데이터 요청)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;실행 위치&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;서버&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;브라우저(클라이언트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;서버가 보내는 것&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;HTML 문서 전체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;JSON 등 필요한 데이터만&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;렌더링 시점&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;요청 시 서버에서 즉시 HTML 생성&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;브라우저가 JS로 DOM 일부만 다시 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;SEO&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;매우 좋음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;가능하지만 불안정함 (엔진마다 JS 실행 능력 다름)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;최초 로딩&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;빠름 (HTML 바로 표시됨)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;JS + 데이터 요청 후 렌더링이라 느릴 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;페이지 이동&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;새 URL 요청 시 HTML을 다시 받음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;전체 페이지는 유지, 일부만 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;hydration&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;필요함&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;이미 hydration된 상태에서 DOM만 추가 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR은 초기 로딩에서 완성된 HTML이 내려오기 때문에 빠르고 SEO도 유리하다.&lt;br /&gt;그리고 SSR 페이지도 초기 렌더 이후에는 AJAX 요청을 사용해 부분 갱신을 수행할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 SSR의 SEO가 CSR보다 더 잘될까?&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://developers.google.com/static/search/docs/images/googlebot-crawl-render-index.png&quot; alt=&quot;구글 공식 검색엔진 처리 과정&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 엔진은 웹을 크롤링하면서 페이지에서 페이지로 링크를 따라가고, 그 HTML 내용을 기반으로 색인을 만든다.&lt;br /&gt;검색 결과에 보이는 정보는 바로 이 &amp;ldquo;수집된 콘텐츠&amp;rdquo;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR의 경우 초기 HTML이 비어 있거나 최소한의 뼈대만 있기 때문에,&lt;br /&gt;JavaScript가 실행되기 전에는 콘텐츠가 존재하지 않는 것처럼 보일 수 있다.&lt;br /&gt;예를 들어 버튼의 문구를 JS로 설정하는 경우, 초기 HTML에는 해당 텍스트가 없기 때문에 크롤링 시 인식되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글은 JavaScript를 실행해 렌더링된 결과를 한 번 더 수집할 수 있지만:&lt;br /&gt;렌더링 큐가 밀릴 수 있고 네트워크 요청이 실패할 수 있으며 다른 검색엔진은 JS 렌더링 능력이 제한적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 CSR 방식은 가능하지만 불안정한 SEO를 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 SSR은 초기 HTML에 이미 모든 주요 콘텐츠가 포함되어 내려오므로&lt;br /&gt;검색 엔진이 즉시 인덱싱할 수 있어 SEO에 매우 안정적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR의 hydration 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 이미 생성되어 올라간 html에 js를 결합하여 사용자의 반응에 따라 작동하도록 만드는 과정이다.&lt;br /&gt;리엑트에서 Hydration은 다음을 의미한다: 기존 DOM과 React 컴포넌트 트리를 비교하고 이벤트 핸들러를 연결하는 과정.&lt;br /&gt;리엑트에서는 html 컴포넌트를 리엑트 dom과 연결하는 함수를 hydrate, hydrateRoot와 같은 이름으로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;hydrateRoot lets you display React components inside a browser DOM node whose HTML content was previously generated by react-dom/server.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리엑트는 DOM, 즉 document object model을 조작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는, 이 과정이 DOM이 클수록 더 오래 걸릴 수 있고,&lt;br /&gt;이 시간이 길어지면 초기 상호작용(클릭, 입력 등)이 지연될 수 있다는 점이다.&lt;br /&gt;다시말해, 페이지가 로드될 동안 다음 프로세스로 진행하지 못하는 지연시간이 발생하거나 정상적으로 유저와 상호작용하지 못할 수도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떨 때 ssr을 선택해야 하는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR이 특히 유리한 경우는 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SEO가 중요한 페이지&lt;/li&gt;
&lt;li&gt;첫 화면에서 핵심 콘텐츠를 빠르게 보여주고 싶은 경우&lt;/li&gt;
&lt;li&gt;사용자별/요청별로 내용이 달라져서 빌드 타임에 미리 HTML을 만들기 어려운 경우&lt;/li&gt;
&lt;li&gt;자주 변경되는 동적 데이터가 필요한 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 완전히 정적 콘텐츠라면 SSG(Static Site Generation) 이 더 효율적일 수 있고,&lt;br /&gt;상호작용이 많고 SEO가 중요하지 않은 페이지는 CSR이 더 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 내용은 추후 더 상세히 포스팅하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/SSR&quot;&gt;MDN - server side rendering&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics?hl=en&quot;&gt;구글 검색센터 -Java Script SEO 기본 사항 이해&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://ko.react.dev/reference/react-dom/client/hydrateRoot&quot;&gt;react hydrateRoot document&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://web.dev/articles/rendering-on-the-web?hl=ko&quot;&gt;Rendering on the Web&lt;/a&gt;&lt;/p&gt;</description>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/56</guid>
      <comments>https://brandofme.tistory.com/entry/SSR%EC%9D%B4%EB%9E%80#entry56comment</comments>
      <pubDate>Mon, 1 Dec 2025 09:11:38 +0900</pubDate>
    </item>
    <item>
      <title>[API]fetch와 axios, jquery.ajax의 차이</title>
      <link>https://brandofme.tistory.com/entry/APIfetch%EC%99%80-axios-jqueryajax%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;h1&gt;ajax란?&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 JavaScript 및 XML(Ajax)은 웹 애플리케이션 개발에 사용되는 기술 그룹이다.&lt;br&gt;페이지를 다시 로드하지 않고 서버 통신을 위해 클라이언트를 시작하는 메소드를 정의하여 부분적으로 페이지를 업데이트할 수 있는 방법을 제공한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;[활용]&lt;br&gt;서버에서 작은 패킷의 데이터가 교환되고 사용자가 입력을 변경할 때마다 웹 페이지가 다시 로드되지 않으므로 보다 향상된 응답 능력을 보여준다. 웹 사이트 상호작용은 페이지의 다시 로드 및 새로 고치기 부분에서만 빠르게 발생한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;[구성]&lt;br&gt;다음 기술 중 하나로 구성&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;정보를 표시하는 XHTML 및 CSS&lt;/li&gt;&lt;li&gt;표시된 정보를 표시하고 동적으로 상호작용하는 DOM(Document Object Model).&lt;/li&gt;&lt;li&gt;웹 서버에서 비동기적으로 데이터를 조작하는 XMLHttpRequest 오브젝트.&lt;/li&gt;&lt;li&gt;데이터 교환 및 조작용 XML, HTML, XSLT&lt;/li&gt;&lt;li&gt;정보 표시와 데이터 요청 바인딩에 사용하는 JavaScript&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;ajax기반 방법과 기본 웹 네트워크 통신의 차이&lt;/h2&gt;&lt;p&gt;&lt;img src=&quot;https://www.ibm.com/docs/ko/SS8PJ7_9.6.1/com.ibm.etools.webtoolscore.doc/images/traditionalWebAppModel.gif&quot; alt=&quot;기본 웹 네트워크 통신&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;기존 웹 애플리케이션에서는 웹 인터페이스에서 사용자 상호작용으로 시작된 HTTP 요청이 웹 서버에서 작성된다. 웹 서버는 요청을 처리하고 클라이언트에 HTML 페이지를 리턴하고, HTTP 전송 중에 사용자는 웹 애플리케이션과 상호작용할 수 없다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://www.ibm.com/docs/ko/SS8PJ7_9.6.1/com.ibm.etools.webtoolscore.doc/images/ajaxWebAppModel.gif&quot; alt=&quot;ajax기반 웹 통신&quot;&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;Ajax 웹 애플리케이션에서 사용자와 웹 애플리케이션의 상호작용은 중단되지 않고, Ajax 엔진 또는 JavaScript 해석기를 사용하면 인터페이스를 렌더링하고 사용자 대신 서버와의 통신을 처리하여 사용자가 서버와의 HTTP 전송과는 독립적으로 웹 애플리케이션과 상호작용할 수 있다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;XMLHttpRequest&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;XMLHttpRequest (XHR) 객체는 서버와 소통할 때 전체 페이지 갱신이 아닌 url에서 데이터만 가져오는 방식으로 통신 가능하다. 유저가 하는 일을 방해하지 않고 페이지가 업데이트될 수 있도록 돕는다. 여기서 받는 데이터는 xml뿐만 아니라 다양한 종류의 데이터를 받을 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;[MDN XMLHttpRequest response 예제]&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const url = &quot;somePage.html&quot;; // A local page

function load(url, callback) {
  const xhr = new XMLHttpRequest();

  xhr.onreadystatechange = () =&amp;gt; {
    if (xhr.readyState === 4) {
      callback(xhr.response);
    }
  };

  xhr.open(&quot;GET&quot;, url, true);
  xhr.send(&quot;&quot;);
}&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;ajax기반 라이브러리 - fetch, axios, jquery.ajax&lt;/h1&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;jquery.ajax&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;** jquery는 자바스크립트 라이브러리이고, HTML 문서 탐색 및 조작, 이벤트 처리, 애니메이션, Ajax 작업 등을 다양한 브라우저에서 작동하는 편리한 API로 훨씬 간단하게 만들어 준다. 대표적으로는 css class, id와 동일한 형식으로 컴포넌트를 탐색, 조작하기 쉽도록 만들어주기도 한다.&lt;br&gt;jquery라는 라이브러리에서 제공하는 비동기 handler인 ajax는 success, error, complete 등의 상태별 콜백 함수를 통해 다양한 처리가 가능하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;[jquery공식사이트 예제]&lt;/p&gt;&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;$.ajax({
  url: &quot;/api/getWeather&quot;,
  data: {
    zipcode: 97201
  },
  success: function( result ) {
    $( &quot;#weather-temp&quot; ).html( &quot;&amp;lt;strong&amp;gt;&quot; + result + &quot;&amp;lt;/strong&amp;gt; degrees&quot; );
  }
});&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;fetch&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Fetch API는 XMLHttpRequest보다 강력하고 유연한 대체제로, HTTP의 주요 구성요소들이 JavaScript 객체로 추상화돼었고, 콜백 기반인 XMLHttpRequest와 달리 모든 API에서 Promise에 기반한 반환을 하기 때문에 활용성이 좋다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;fetch 핵심 구성 요소&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Fetch의 핵심은 HTTP Request, Response, Headers를 추상화하는 인터페이스와, 비동기적 리소스 요청을 시작하기 위한 fetch() 메서드이다&lt;br&gt;&amp;nbsp;&lt;br&gt;[MDN 예제 코드]&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// POST 메서드 구현 예제
async function postData(url = &quot;&quot;, data = {}) {
  // 옵션 기본 값은 *로 강조
  const response = await fetch(url, {
    method: &quot;POST&quot;, // *GET, POST, PUT, DELETE 등
    mode: &quot;cors&quot;, // no-cors, *cors, same-origin
    cache: &quot;no-cache&quot;, // *default, no-cache, reload, force-cache, only-if-cached
    credentials: &quot;same-origin&quot;, // include, *same-origin, omit
    headers: {
      &quot;Content-Type&quot;: &quot;application/json&quot;,
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: &quot;follow&quot;, // manual, *follow, error
    referrerPolicy: &quot;no-referrer&quot;, // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data), // body의 데이터 유형은 반드시 &quot;Content-Type&quot; 헤더와 일치해야 함
  });
  return response.json(); // JSON 응답을 네이티브 JavaScript 객체로 파싱
}

postData(&quot;https://example.com/answer&quot;, { answer: 42 }).then((data) =&amp;gt; {
  console.log(data); // JSON 데이터가 `data.json()` 호출에 의해 파싱됨
});&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;axios&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트이다. 동일한 코드베이스로 브라우저와 node.js에서 실행할 수 있다. 서버 사이드에서는 네이티브 node.js의 http 모듈을 사용하고, 클라이언트(브라우저)에서는 XMLHttpRequest를 사용한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;[특징]&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;브라우저를 위해 XMLHttpRequest 생성&lt;/li&gt;&lt;li&gt;node.js를 위해 http 요청 생성&lt;/li&gt;&lt;li&gt;Promise API를 지원&lt;/li&gt;&lt;li&gt;요청 및 응답 인터셉트&lt;/li&gt;&lt;li&gt;요청 및 응답 데이터 변환&lt;/li&gt;&lt;li&gt;요청 취소&lt;/li&gt;&lt;li&gt;JSON 데이터 자동 변환&lt;/li&gt;&lt;li&gt;XSRF를 막기위한 클라이언트 사이드 지원&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;fetch와 달리 axios에서는 요청 및 응답 전 인터셉트, 요청 취소 등의 다양한 기능을 제공한다.&lt;br&gt;&lt;br&gt;[axios 공식 홈페이지 - 요청 취소 예제]&lt;/p&gt;&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const controller = new AbortController();

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token,
  signal: controller.signal
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 에러 핸들링
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 요청 취소하기 (메시지 파라미터는 옵션입니다)
source.cancel('Operation canceled by the user.');
// OR
controller.abort(); // 메시지 파라미터 지원하지 않음
&lt;/code&gt;&lt;/pre&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;세 라이브러리+API의 활용방안 비교&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;현재 jquery 라이브러리 사용률이 떨어지다보니 jquery_ajax도 활용률이 떨어지는 것 같다.&lt;br&gt;&lt;br&gt;라이브러리 의존도를 낮추고 싶자면 브라우저 및 이제 넥스트, 노드에서 기본으로 제공되는 fetch API를, 추가 기능을 별도 정의하고 싶지 않다면 편리한 axios를 사용하는 듯하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;노드가 아닌 넥스트 기반에서 주로 사용하는 나라면 주로 fetch API를 활용할 것 같다.&lt;br&gt;왜냐하면 넥스트에서 제공하는 캐시 기능이 axios와 충돌하여 불필요한 에러가 생긴다는 이야기가 있었기 때문이다.&lt;br&gt;그러나 2024년 관련 어뎁터로 구조를 변경한 예시가 발표되었다.&lt;br&gt;그렇지만 편리성보다는 아직 교육, 배움 목적이 크기도 하고, 넥스트, 리엑트 자체에서 fetch기반으로 확장되고 있는 추세니까 아무래도 기본을 중심으로 사용할 것 같다. 익숙해지면 axios를 써서 편리하게 단축하는 편이 현재 목적에는 맞는 것 같다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/docs/ko/rational-soft-arch/10.0?topic=SS8PJ7_10.0/com.ibm.etools.webtoolscore.doc/topics/cajax.htm&quot; target=&quot;_self&quot;&gt;&lt;span&gt;IBM AJAX&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response&quot; target=&quot;_self&quot;&gt;&lt;span&gt;MDN XMLHttpRequest&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://api.jquery.com/jQuery.ajax/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;jquery-ajax&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://axios-http.com/kr/docs/intro&quot; target=&quot;_self&quot;&gt;&lt;span&gt;axios document&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/fetch&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Mdn fetch()&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://velog.io/@bbbjihan/Next.js-%EC%BA%90%EC%8B%B1-%EB%95%8C%EB%AC%B8%EC%97%90-Axios-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0%EB%A5%BC-%ED%8F%AC%EA%B8%B0%ED%95%98%EB%9D%BC%EA%B3%A0-Axios-adapter-%EC%84%A4%EC%A0%95%EC%9C%BC%EB%A1%9C-Next.js-caching-%EC%A7%80%EC%9B%90-%EB%B0%9B%EA%B8%B0&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Next.js 캐싱 때문에 Axios 인터셉터를 포기하라고? Axios adapter 설정으로 Next.js caching 지원 받기&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev Study/WEB</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/55</guid>
      <comments>https://brandofme.tistory.com/entry/APIfetch%EC%99%80-axios-jqueryajax%EC%9D%98-%EC%B0%A8%EC%9D%B4#entry55comment</comments>
      <pubDate>Sun, 23 Nov 2025 21:41:23 +0900</pubDate>
    </item>
    <item>
      <title>API의 구조 - HTTP 기반</title>
      <link>https://brandofme.tistory.com/entry/API%EC%9D%98-%EA%B5%AC%EC%A1%B0-HTTP-%EA%B8%B0%EB%B0%98</link>
      <description>&lt;h1&gt;API의 기본 구성요소&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 요청, 서버, API 응답으로 구성되어있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.redhat.com/rhdc/managed-files/API-page-graphic.png&quot; alt=&quot;RedHat_API Image&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;요청은 클라이언트의 정보를 포함하고 서버가 이를 처리하며, 응답은 서버에서 제공한 결과 또는 데이터를 포함한다.&lt;br /&gt;이 때 요청하는 앱을 클라이언트, 응답하는 앱을 서버라고 한다.&lt;/p&gt;
&lt;h1&gt;HTTP MESSAGE&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 기본 구조는 HTTP 메시지 구조와 거의 동일하다.&lt;br /&gt;REST 서비스, 혹은 웹기반 API의 경우 서버와 클라이언트간 통신에 일반적으로 HTTP를 기반으로 RESTfull API를 구현하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://mdn.github.io/shared-assets/images/diagrams/http/messages/http-message-anatomy.svg&quot; alt=&quot;MDN - HTTP MESSAGE IMAGE&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 줄은 HTTP의 버전 정보를 요청하는 메소드 혹은 요청의 결과와 함께 전달한다.&lt;br /&gt;HTTP 헤더는 선택적으로 메타데이터와 메세지를 포함할 수 있다.&lt;br /&gt;빈 줄을 통해 메타데이터와 메세지가 종료되었음을 안내한다&lt;br /&gt;그 뒤에 선택적으로 메세지와 연관된 데이터를 보낼 수 있다. http 헤더의 첫번째 줄과 헤더에 따라 바디가 있을 것인지 알려준다.&lt;br /&gt;첫번째 줄과 헤더는 헤드로, 그 뒤에 이어지는 것은 바디로 부르기도 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;API Endpoint&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에서 사용가능한 각 URL을 나타낸다. 엔드포인트는 요청을 다르게 할 수 있는 방법을 나타내며, 각 엔드포인트는 고유한 특정 기능을 갖고 있다.&lt;br /&gt;이 엔드포인트는 디지털 게이트웨이 또는 API 내에서 요청을 수신하고 응답, 전송하는 특정 위치이다. API 상호작용을 위한 진입점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API 게이트웨이&lt;/b&gt; : 광범위한 백엔드 서비스를 사용하는 기업 클라이언트를 위한 API 관리도구, 사용자 인증, 통계 및 속도 관리와 같은 일반적인 태스크 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 서버 URL, 서비스 및 시스템 간에 정보가 송수신되는 특정 디지털 위치가 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;https://www.googleapis.com/youtube/v3/channels&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 youtube data api의 channel 관련된 데이터의 엔드포인트이다.&lt;br /&gt;googleapis경로의 youtube/v3 -&amp;gt; youtube data api v3의 channel관련된 경로이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 url에 원하는 파라미터를 추가하여 더 상세히 원하는 응답을 요청할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;https://youtube.googleapis.com/youtube/v3/channels?mine=true&amp;amp;key=[YOUR_API_KEY]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이렇게 설정하면 ? 뒤에 queryParams를 더해서 결과를 좁힐 수 있다.&lt;br /&gt;mine=true옵션을 통해서 내 것만 불러오도록 할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요청(Request)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 기반 API, 즉 거의 모든 웹 API는 HTTP 요청 메서드를 사용하여 원하는 작업을 나타낸다.&lt;br /&gt;주요 HTTP 메서드는 다음과 같다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;Method&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Description&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Parameter Location&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Return Type&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;데이터 조회&lt;/td&gt;
&lt;td&gt;URL 쿼리스트링&lt;/td&gt;
&lt;td&gt;JSON, HTML&lt;/td&gt;
&lt;td&gt;서버변경X, 캐싱 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;데이터 생성/등록&lt;/td&gt;
&lt;td&gt;Request Body&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;서버변경O, 비멱등성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;전체 갱신/덮어쓰기&lt;/td&gt;
&lt;td&gt;Request Body, URL&lt;/td&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;서버변경O, 멱등성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;데이터 삭제&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;메시지, 상태&lt;/td&gt;
&lt;td&gt;서버변경O, 멱등성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멱등성&lt;/b&gt; : 동일한 연산을 여러 번 적용해도 결과가 처음 한 번 적용했을 때와 달라지지 않는 성질&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 GET 메서드의 경우에는 URL 쿼리 스트링으로 &lt;b&gt;parameter(요청 매개변수)&lt;/b&gt; 를 전달한다.&lt;br /&gt;아까 예를 들었던 채널 api 엔드포인트 사례를 살펴보면,&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;GET https://youtube.googleapis.com/youtube/v3/channels?mine=true&amp;amp;key=[YOUR_API_KEY]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;? 뒤에 있는 것이 url 쿼리 파라미터이다.&lt;br /&gt;GET은 데이터를 가져오기만 하기 때문에 서버를 변경하지 않고, 멱등성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST는 특정 리소스에 엔티티를 제출할 때 쓰인다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;POST /api/users
Content-Type: application/json

{
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;email&quot;: &quot;john@example.com&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 users에 새로운 데이터를 추가하는 명령어이다.&lt;br /&gt;parameter는 &lt;b&gt;Request body&lt;/b&gt;에 포함된다. 서버의 상태의 변화를 일으킨다.&lt;br /&gt;또한 Content-Type이란 표준 헤더에 필요한 응답 형식인 json을 지정하였다.&lt;br /&gt;요청할 때 &lt;b&gt;요청 헤더&lt;/b&gt;를 활용하여 API에 추가적인 컨텍스트와 기능을 제공하도록 요청할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;PUT /api/users/123
Content-Type: application/json

{
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;email&quot;: &quot;john_new@example.com&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PUT은 목적 리소스 모든 현재 표시를 요청 payload(본문)으로 바꾼다.&lt;br /&gt;본문은 JSON, XML 또는 기타 형식으로 포맷할 수 있다.&lt;br /&gt;본문을 통해 개별 매개변수가 아닌 전체 리소스나 데이터 셋을 전송할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;DELETE /api/users/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DELET는 특정 리소스를 삭제한다. 파라미터는 URL에 담긴다.&lt;br /&gt;여기서는 123번 키값의 유저를 삭제하라고 선언했다.&lt;br /&gt;이런 API 명령 반응 실험은 API POSTMAN과 같은 테스트 도구에서 실행해볼 수 있다.&lt;br /&gt;아니면 완성후 개발환경에서 빌드된 상태로 해당 경로를 통해 접근해볼 수도 있다.&lt;br /&gt;직접 url 기준으로 들어가보는 것으로 접근도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;payload&lt;/b&gt;: 요청이나 응답의 본문 (body)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;응답 (Response)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답은 요청된 데이터 또는 결과를 제공한다. 상태코드와 응답 헤더 등을 포함할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상태코드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;200 OK: 요청 성공&lt;/li&gt;
&lt;li&gt;201 Created: 성공적 생성&lt;/li&gt;
&lt;li&gt;400 Bad Request: 잘못된 요청&lt;/li&gt;
&lt;li&gt;404 Not Found : 요청 리소스 없음&lt;/li&gt;
&lt;li&gt;500 Server Error : 내부 서버 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/Messages#%EA%B2%B0%EB%A1%A0&quot;&gt;HTML message&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://apidog.com/kr/blog/what-are-the-components-of-an-api-2/&quot;&gt;API의 구성 요소는 무엇인가요?&lt;/a&gt;&lt;/p&gt;</description>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/54</guid>
      <comments>https://brandofme.tistory.com/entry/API%EC%9D%98-%EA%B5%AC%EC%A1%B0-HTTP-%EA%B8%B0%EB%B0%98#entry54comment</comments>
      <pubDate>Sun, 16 Nov 2025 19:39:49 +0900</pubDate>
    </item>
    <item>
      <title>API의 정의와 API의 종류 (REST, SOAP)</title>
      <link>https://brandofme.tistory.com/entry/API%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-API%EC%9D%98-%EC%A2%85%EB%A5%98-REST-SOAP</link>
      <description>&lt;h1&gt;Application Programming Interface&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 &amp;ldquo;정의 및 프로토콜 집합을 사용하여 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘&amp;rdquo;이다&lt;br /&gt;여기서 앱(application)은 고유한 기능을 가진 모든 소프트웨어이고, 인터페이스는 두 앱 간의 서비스 계약이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 계약은 요청과 응답을 사용하여 서로 통신하는 방법을 정의한다.&lt;br /&gt;API 문서에는 개발자가 이러한 요청과 응답을 구성하는 방법에 대한 정보가 들어있다.&lt;/p&gt;
&lt;h1&gt;API의 종류&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API의 종류는 크게 두 가지 관점에서 분류할 수 있다.&lt;br /&gt;첫번째 관점은 사용자층이고 두번째 관점은 구성 방법론이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자층에 따른 API의 분류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프라이빗 : API를 내부에서만 사용할 수 있도록 하며, 기업이 API를 최대한으로 제어할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예 ) 사내 백엔드와 WAS(Web Application Server), 프론트엔드를 연결하는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파트너 : API를 특정 비즈니스 파트너와 공유하며, 품질 저하없이 추가 수익원을 창출할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예) 파트너사에게 공유되는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;퍼블릭: API가 모두에게 제공되며, 제 3자가 API와 상호작용하는 애플리케이션 개발하여 혁신을 이끌어낼 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenAPI, Google Maps, Youtube data api&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성 방법론에 따른 API의 분류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성 방법론의 대표적 종류 2가지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SOAP(Simple Object Access Protocol)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XML 메시지 형식을 사용하며 HTTP 또는 SMTP를 통해 요청을 수신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;REST(Representational State Transfer)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST는 SOAP와 달리 프로토콜이 아닌 아키텍처로 이를 기반으로 한 API를 REST API, REST를 잘 따른 API를 RESTful API라고 하는데 공식적인 표준이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 프로토콜: 데이터가 상호작용하는 규칙&lt;br /&gt;*&lt;/b&gt; 아키텍처: 전체 디자인 체계에 대한 가이드 규칙의 집합이다.&lt;br /&gt;예를 들어 SOAP에서는 데이터 리턴 타입이 특정한 조건을 지켜야 한다고 제시한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot;?&amp;gt;

&amp;lt;soap:Envelope
xmlns:soap=&quot;http://www.w3.org/2003/05/soap-envelope&quot;
soap:encodingStyle=&quot;http://www.w3.org/2003/05/soap-encoding&quot;&amp;gt;

&amp;lt;soap:Header&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 REST에서는 HTTP 프로토콜을 사용하라고 하고 다음과 같은 몇 가지 가이드라인을 제시한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;6가지 주요 제약 조건&amp;gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 서버 아키텍처: REST 아키텍처가 클라이언트, 서버, 리소스로 구성되며 HTTP를 통해 요청을 처리한다&lt;/li&gt;
&lt;li&gt;스테이트리스: 요청이 통과하는 서버에는 클라이언트 콘텐츠가 저장되지 않으며 그 대신 세션 상태에 대한 정보가 클라이언트에 저장된다.&lt;/li&gt;
&lt;li&gt;캐시 가능성: 캐싱으로 일부 클라이언트-서버 간의 상호작용이 제거된다.&lt;/li&gt;
&lt;li&gt;계층화된 시스템: 추가 계층으로 클라이언트-서버 간의 상호 작용을 조정할 수 있으며 이러한 계층은 로드 밸런싱, 공유 캐시 또는 보안과 같은 추가 기능을 제공할 수 있다&lt;/li&gt;
&lt;li&gt;코드 온디맨드(옵션): 서버가 실행 가능한 코드를 전송하여 클라이언트의 기능을 확대할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합된 인터페이스&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 제약 조건은 RESTful API 설계의 핵심이며 다음과 같은 4가지 측면을 포함합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청에서 리소스 식별:&lt;/b&gt; 리소스가 요청에서 식별되며 클라이언트로 반환된 표현으로부터 분리됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;표현을 통한 리소스 조작:&lt;/b&gt; 클라이언트가 리소스를 나타내는 파일을 수신합니다. 이 표현에는 조작 또는 삭제를 허용할 수 있도록 충분한 양의 정보가 포함되어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자기 기술적(Self-descriptive) 메시지:&lt;/b&gt; 클라이언트에 반환되는 각 메시지에 클라이언트가 정보를 어떻게 처리해야 할지 설명하는 정보가 충분히 포함되어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 상태 엔진으로서의 하이퍼미디어:&lt;/b&gt; 리소스를 할당한 후 REST 클라이언트가 하이퍼링크를 통해 현재 사용 가능한 기타 모든 작업을 찾을 수 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규정된 프로토콜보다는 훨씬 간단하여 RESTful API가 많이 사용되고 있다. OpenAPI사양은 REST API를 정의하는 공통 표준으로 부상했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*** OpenAPI 사양: RESTful API를 설명하고 정의하기 위한 표준화된 인터페이스 정의 언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그 외에도 쿼리 언어이자 서버측 런타임으로 REST의 대안인 GraphQL이 있다. 이것은 클라이언트에게 요청한 만큼의 데이터를 제공하는 데 우선순위를 둔다.&lt;br /&gt;GraphQL은 개발자가 단일 API호출로 다양한 데이터 소스에서 데이터를 끌어오는 요청을 구성할 수 있도록 지원한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고문서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/api/what-are-application-programming-interfaces&quot;&gt;API란? 기본 개념, 개발 방식, 종류, 서비스, Web API 연동 방법&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/api/&quot;&gt;API란 무엇인가요? - 애플리케이션 프로그래밍 인터페이스 설명 - AWS&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.reddit.com/r/learnprogramming/comments/1nlqulv/what_makes_soap_a_protocol_but_rest_an/&quot;&gt;Reddit- What makes SOAP a protocol but REST an &amp;ldquo;architectural style&amp;rdquo;? I thought a protocol refers to the transport medium so I figured SOAP wouldn&amp;rsquo;t be a protocol either.&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev Study/DEV</category>
      <category>API</category>
      <category>rest</category>
      <category>soap</category>
      <author>now.lee</author>
      <guid isPermaLink="true">https://brandofme.tistory.com/53</guid>
      <comments>https://brandofme.tistory.com/entry/API%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-API%EC%9D%98-%EC%A2%85%EB%A5%98-REST-SOAP#entry53comment</comments>
      <pubDate>Sun, 16 Nov 2025 18:35:34 +0900</pubDate>
    </item>
  </channel>
</rss>