<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kim__0916.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 취준생입니다.</description>
        <lastBuildDate>Mon, 11 May 2026 00:16:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kim__0916.log</title>
            <url>https://velog.velcdn.com/images/gayeong__0916/profile/ea2d2048-4550-423a-ac9c-758544e0c2cc/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kim__0916.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gayeong__0916" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[토스 페이먼츠 next.js 연동]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%86%A0%EC%8A%A4-%ED%8E%98%EC%9D%B4%EB%A8%BC%EC%B8%A0-next.js-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@gayeong__0916/%ED%86%A0%EC%8A%A4-%ED%8E%98%EC%9D%B4%EB%A8%BC%EC%B8%A0-next.js-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Mon, 11 May 2026 00:16:03 GMT</pubDate>
            <description><![CDATA[<pre><code>npm install @tosspayments/tosspayments-sdk</code></pre><pre><code>  const clientKey =
    process.env.NEXT_PUBLIC_TOSS_CLIENT_KEY ??
    &quot;test_ck_DpexMgkW36oWNvJzApKEVGbR5ozO&quot;;</code></pre><ul>
<li><p>이 버전은 비회원도 가능</p>
<pre><code>const handlePayment = async () =&gt; {
  if (!selected) {
    alert(&quot;충전 금액을 선택해주세요.&quot;);
    return;
  }

  // &quot;100,000원&quot; -&gt; 100000 (숫자)로 변환
  const amount = Number(selected.replace(/[^0-9]/g, &quot;&quot;));

  try {
    const tossPayments = await loadTossPayments(clientKey);

    // 결제창 띄우기
    const payment = tossPayments.payment({
      customerKey: &quot;ANONYMOUS&quot;, // 비회원 결제 시 필수값 (랜덤값도 가능)
    });

    await payment.requestPayment({
      method: &quot;CARD&quot;, // 결제 수단
      amount: {
        currency: &quot;KRW&quot;,
        value: amount,
      },
      orderId: `CHARGE-${Math.random().toString(36).slice(2, 11)}`,
      orderName: `캐시 ${selected} 충전`,
      successUrl: `${window.location.origin}/payment/success`,
      failUrl: `${window.location.origin}/payment/fail`,
    });
  } catch (error) {
    console.error(&quot;결제 요청 에러:&quot;, error);
  }
};
</code></pre></li>
</ul>
<pre><code>
- 로그인한 회원만 가능할 때</code></pre><p>&quot;use client&quot;;</p>
<p>import { useSession } from &quot;next-auth/react&quot;; // 예시: NextAuth 사용 시
// 또는 자신이 사용하는 Auth Hook (zustand, react-query 등)</p>
<p>export default function ChargeModal({ open, onClose }: ModalProps) {
  // 1. 로그인된 유저 정보 가져오기
  const { data: session } = useSession(); 
  const userIdentifier = session?.user?.id || &quot;ANONYMOUS&quot;; // 유저 고유 ID</p>
<p>  const handlePayment = async () =&gt; {
    // ... (금액 변환 로직 동일)</p>
<pre><code>try {
  const tossPayments = await loadTossPayments(clientKey);

  // 2. 고유 식별자를 customerKey에 할당
  const payment = tossPayments.payment({
    customerKey: userIdentifier, 
  });

  await payment.requestPayment({
    method: &quot;CARD&quot;,
    amount: {
      currency: &quot;KRW&quot;,
      value: amount,
    },
    orderId: `CHARGE-${Date.now()}-${userIdentifier.slice(0, 5)}`, // 주문번호에 유저 정보 살짝 섞기
    orderName: `캐시 ${selected} 충전`,
    // 3. 성공 시 우리 서버로 유저 정보를 같이 넘기기 위해 세팅 (선택사항)
    successUrl: `${window.location.origin}/payment/success?userId=${userIdentifier}`,
    failUrl: `${window.location.origin}/payment/fail`,
    customerEmail: session?.user?.email, // 유저 이메일 (결제창에 자동 입력됨)
    customerName: session?.user?.name,   // 유저 이름
  });
} catch (error) {
  console.error(error);
}</code></pre><p>  };</p>
<p>  // ... (UI 코드)
}</p>
<pre><code>
무조건 성공 또는 실패 후 페이지 이동 필수
</code></pre><p>&quot;use client&quot;;</p>
<p>import Modal from &quot;@/shared/ui/Modal&quot;;
import { useState } from &quot;react&quot;;</p>
<p>const SuccessPage = () =&gt; {
  const [successModalOpen, setSuccessModalOpen] = useState(true);</p>
<p>  return (
    &lt;Modal
      actions={[{ label: &quot;확인&quot;, onClick: () =&gt; {}, variant: &quot;green&quot; }]}
      title=&quot;결제 성공&quot;
      description=&quot;성공적으로 결제가 완료되었습니다.&quot;
      open={successModalOpen}
      onClose={() =&gt; {
        setSuccessModalOpen(false);
      }}
    /&gt;
  );
};</p>
<p>export default SuccessPage;</p>
<pre><code>
- 백엔드에게 정보 보내기</code></pre><p>const SuccessPage = () =&gt; {
  const [successModalOpen, setSuccessModalOpen] = useState(true);
  const searchParams = useSearchParams();</p>
<p>  useEffect(() =&gt; {
    // 1. URL에서 토스가 보내준 파라미터 추출
    const paymentKey = searchParams.get(&quot;paymentKey&quot;);
    const orderId = searchParams.get(&quot;orderId&quot;);
    const amount = searchParams.get(&quot;amount&quot;);</p>
<pre><code>const confirmPayment = async () =&gt; {
  try {
    // 2. 내 백엔드 서버(API)로 결제 승인 요청 보내기
    // BASE_URL은 아까 적어주신 http://123.142.202.212:8081/api/web 등을 활용하세요.
    const response = await axios.post(
      `${process.env.NEXT_PUBLIC_API_BASE_URL}/payments/confirm`,
      {
        paymentKey,
        orderId,
        amount: Number(amount),
      },
    );

    if (response.status === 200) {
      // 서버에서 최종 승인 성공 시 모달 띄우기
      setSuccessModalOpen(true);
    }
  } catch (error: any) {
    console.error(&quot;승인 실패:&quot;, error.response?.data || error.message);
    // 승인 실패 시 실패 페이지로 강제 이동하거나 에러 모달 처리
    router.push(
      `/billing/payment/fail?code=${error.response?.data?.code}&amp;message=${error.response?.data?.message}`,
    );
  } finally {
    setLoading(false);
  }
};

if (paymentKey &amp;&amp; orderId &amp;&amp; amount) {
  confirmPayment();
}</code></pre><p>  }, [searchParams, router]);</p>
<p>  if (loading) return <div>결제 승인 처리 중입니다...</div>;</p>
<p>  return (
    &lt;Modal
      actions={[{ label: &quot;확인&quot;, onClick: () =&gt; {}, variant: &quot;green&quot; }]}
      title=&quot;결제 성공&quot;
      description=&quot;성공적으로 결제가 완료되었습니다.&quot;
      open={successModalOpen}
      onClose={() =&gt; {
        setSuccessModalOpen(false);
      }}
    /&gt;
  );
};</p>
<pre><code>
실패
- 결제 시간 초과
- 결제 실패

- 토스에서 제공하는 에러 코드
PAY_PROCESS_ABORTED 사용자가 결제창을 닫음 &quot;결제가 취소되었습니다&quot; 
REJECT_CARD_PAYMENT 카드사 거절 (잔액부족 등) &quot;결제에 실패했습니다&quot; 
PAY_PROCESS_TIMEOUT 결제 시간 초과 &quot;시간 초과&quot; 
PROVIDER_TIMEOUT    카드사/은행 응답 초과 &quot;시간 초과&quot;

에러코드가 토스페이먼츠의 기본 작동 방식이 &quot;브라우저 주소창(URL)에 에러 정보를 담아서 약속된 페이지로 던져주는 것&quot;이기 때문입니다.
ex)만약 failUrl을 `https://mysite.com/payment/fail` 로 설정했다면, 실제 에러 발생 시 브라우저 주소창은 다음과 같이 변합니다:
</code></pre><p><a href="https://mysite.com/payment/fail?code=PAY_PROCESS_TIMEOUT&amp;message=%EA%B2%B0%EC%A0%9C%EC%8B%9C%EA%B0%84%EC%9D%B4+%EC%B4%88%EA%B3%BC%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4">https://mysite.com/payment/fail?code=PAY_PROCESS_TIMEOUT&amp;message=결제시간이+초과되었습니다</a></p>
<pre><code></code></pre><p>&quot;use client&quot;;</p>
<p>import Modal from &quot;@/shared/ui/Modal&quot;;
import { useRouter, useSearchParams } from &quot;next/navigation&quot;;
import { useState } from &quot;react&quot;;</p>
<p>const FailPage = () =&gt; {
  const searchParams = useSearchParams();
  const router = useRouter();
  const [failModalOpen, setFailModalOpen] = useState(true);</p>
<p>  const errorCode = searchParams.get(&quot;code&quot;);</p>
<p>  const isTimeout =
    errorCode === &quot;PAY_PROCESS_TIMEOUT&quot; || errorCode === &quot;PROVIDER_TIMEOUT&quot;;
  const isAborted = errorCode === &quot;PAY_PROCESS_ABORTED&quot;; // 사용자가 직접 닫음</p>
<p>  const handleRetry = () =&gt; {
    router.push(&quot;/billing&quot;);
  };</p>
<p>  return (
    &lt;Modal
      actions={[{ label: &quot;확인&quot;, onClick: () =&gt; {}, variant: &quot;white&quot; }]}
      title=&quot;결제 실패&quot;
      description={
        isTimeout
          ? &quot;결제 가능 시간을 초과하였습니다. 다시 시도해주세요.&quot;
          : isAborted
            ? &quot;결제가 취소되었습니다.&quot;
            : &quot;결제 요청에 실패했습니다. 다시 시도해주세요.&quot;
      }
      open={failModalOpen}
      onClose={() =&gt; {
        setFailModalOpen(false);
        handleRetry();
      }}
    /&gt;
  );
};</p>
<p>export default FailPage;</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[refresh API는 왜 반드시 별도 axios 인스턴스로 분리해야 할까?]]></title>
            <link>https://velog.io/@gayeong__0916/refresh-API%EB%8A%94-%EC%99%9C-%EB%B0%98%EB%93%9C%EC%8B%9C-%EB%B3%84%EB%8F%84-axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@gayeong__0916/refresh-API%EB%8A%94-%EC%99%9C-%EB%B0%98%EB%93%9C%EC%8B%9C-%EB%B3%84%EB%8F%84-axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 04 May 2026 06:23:52 GMT</pubDate>
            <description><![CDATA[<ul>
<li>refresh API가 기존 axios 인스턴스를 타면, 자기 자신을 가로채는 interceptor 때문에 무한 루프가 생길 수 있다.</li>
</ul>
<h3 id="기본-axios-인스턴스api는-토큰-관리용이다">기본 axios 인스턴스(api)는 “토큰 관리용”이다</h3>
<p>보통 우리가 쓰는 api 인스턴스에는 이런 역할이 들어가 있음</p>
<ul>
<li>request interceptor<ul>
<li>accessToken 자동 첨부</li>
</ul>
</li>
<li>response interceptor<ul>
<li>401 감지</li>
<li>refresh 시도</li>
<li>실패 요청 재시도</li>
</ul>
</li>
</ul>
<p>즉, api = “인증이 필요한 일반 API 요청 전용 인스턴스”</p>
<h3 id="만약-refresh-api도-api-인스턴스를-쓰면">만약 refresh API도 api 인스턴스를 쓰면?</h3>
<ol>
<li>어떤 API 요청이 401을 맞음</li>
<li>response interceptor 진입</li>
<li>refresh를 위해 /token/access 호출</li>
<li>!!! 그런데 이 요청도 api 인스턴스를 탐</li>
<li>→ request interceptor 실행<ul>
<li>accessToken 붙임 (이미 만료된 토큰)</li>
</ul>
</li>
<li>→ 서버에서 refreshToken도 만료 / 문제 발생</li>
<li>→ /token/access 요청도 401 응답</li>
<li>→ response interceptor 다시 진입</li>
<li>“어? 401이네? refresh 해야지”</li>
<li>다시 /token/access 호출 …</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axios Interceptor로 Access Token 자동 갱신하기 (Refresh Token + Queue 처리)]]></title>
            <link>https://velog.io/@gayeong__0916/Axios-Interceptor%EB%A1%9C-Access-Token-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0%ED%95%98%EA%B8%B0-Refresh-Token-Queue-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@gayeong__0916/Axios-Interceptor%EB%A1%9C-Access-Token-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0%ED%95%98%EA%B8%B0-Refresh-Token-Queue-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 28 Jan 2026 00:00:18 GMT</pubDate>
            <description><![CDATA[<h2 id="request-인터셉터">request 인터셉터</h2>
<ol>
<li>토큰을 어디에 넣을지</li>
<li>헤더 형식
Request Interceptor: Access Token 자동 첨부</li>
</ol>
<p>Access Token은 SessionStorage에 저장해두고, 모든 요청에 자동으로 붙인다.</p>
<pre><code>api.interceptors.request.use(
  (config) =&gt; {
    if (typeof window !== &quot;undefined&quot;) {
      const accessToken = SessionStorage.getItem(&quot;accessToken&quot;);

      if (accessToken) {
        config.headers.Authorization = `Bearer ${accessToken}`;
      }
    }

    return config;
  },
  (error) =&gt; Promise.reject(error),
);</code></pre><h2 id="response-인터셉터">response 인터셉터</h2>
<ul>
<li>“언제 재발급할지” 규칙만 맞추면 됨</li>
</ul>
<ol>
<li>어떤 상태 코드에서 refresh 할지</li>
<li>refresh API가 어떤 응답을 주는지</li>
<li>refresh 실패 시 어떻게 처리할지</li>
</ol>
<p>Access Token이 만료되면 보통 401 Unauthorized 가 떨어진다.
이때 refresh API를 호출해서 새 access token을 받아오고, 실패했던 요청을 다시 실행한다.
주의: 동시에 여러 요청이 401로 터질 수 있으므로, refresh 요청을 한 번만 보내고 나머지는 대기시켜야 한다. (큐 처리)
별도 인스턴스 만드는거 권장 -&gt; url이 달라도 같은 axios 인스턴스 탈 수 있기 때문에</p>
<ul>
<li><code>isRefreshing</code> : 현재 refresh 요청이 진행 중인지 여부</li>
<li><code>failedQueue</code> : refresh가 끝날 때까지 대기할 요청들의 Promise resolve/reject 모음<pre><code>type FailedQueueItem = {
resolve: (token: string) =&gt; void;
reject: (err: unknown) =&gt; void;
};
</code></pre></li>
</ul>
<p>let isRefreshing = false;
let failedQueue: FailedQueueItem[] = [];</p>
<p>const processQueue = (error: unknown, token: string | null) =&gt; {
  failedQueue.forEach((p) =&gt; (token ? p.resolve(token) : p.reject(error)));
  failedQueue = [];
};</p>
<p>api.interceptors.response.use(
  (res) =&gt; res,
  async (error) =&gt; {
    const originalRequest = error.config as any;</p>
<pre><code>// 1) 토큰 만료 조건: 보통 401
const isExpired = error.response?.status === 401;

// 이미 retry한 요청이면 무한루프 방지
if (!isExpired || originalRequest._retry) {
  return Promise.reject(error);
}

// 2) 이미 refresh 중이면 큐에 대기
if (isRefreshing) {
  return new Promise((resolve, reject) =&gt; {
    failedQueue.push({
      resolve: (newToken) =&gt; {
        originalRequest.headers.Authorization = `Bearer ${newToken}`;
        resolve(api(originalRequest));
      },
      reject,
    });
  });
}

originalRequest._retry = true;
isRefreshing = true;

try {
  // 3) 여기서 /token/access 호출 = refresh api 호출
  // refresh token은 httpOnly 쿠키로 자동 전송됨
  const res = await refreshApi.post(&quot;/token/access&quot;);

  // 4) 서버 응답 구조에 맞게 accessToken 추출
  // { data: { accessToken: &quot;...&quot; } } 또는 { data: { data: { accessToken } } } 등
  const newAccessToken = res.data?.accessToken;

  if (!newAccessToken)
    throw new Error(&quot;No accessToken in refresh response&quot;);

  // 5) 새 토큰 저장
  sessionStorage.setItem(&quot;accessToken&quot;, newAccessToken);

  // 대기 중 요청들 처리
  processQueue(null, newAccessToken);

  // 6) refresh를 수행한 요청(A) 재시도
  originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
  return api(originalRequest);
} catch (refreshError) {
  // refresh 실패 → 모든 요청 실패 처리 + 로그아웃
  processQueue(refreshError, null);
  sessionStorage.removeItem(&quot;accessToken&quot;);
  window.location.href = &quot;/login&quot;;
  return Promise.reject(refreshError);
} finally {
  isRefreshing = false;
}</code></pre><p>  },
);</p>
<pre><code>### 실행 과정
여러 API 요청(A, B, C)이 동시에 나갔는데
accessToken이 만료된 상태로 가정

1️⃣ A 요청
- A → 401
- isRefreshing = false
- isRefreshing = true로 변경
- /token/access 호출 (refresh 시작)

2️⃣ B 요청
- B → 401
- 이미 isRefreshing === true
- 아래 코드로 진입</code></pre><p>if (isRefreshing) {
  return new Promise((resolve, reject) =&gt; {
    failedQueue.push({
      resolve: (newToken) =&gt; {
        originalRequest.headers.Authorization = <code>Bearer ${newToken}</code>;
        resolve(api(originalRequest)); // B 재요청 예약
      },
      reject,
    });
  });
}</p>
<pre><code>- 새로운 Promise를 생성
- resolve / reject를 큐에 저장
- B는 Promise pending 상태로 대기

3️⃣ refresh 성공 (A 흐름 복귀)</code></pre><p>processQueue(null, newAccessToken);</p>
<p>const processQueue = (error: unknown, token: string | null) =&gt; {
  failedQueue.forEach((p) =&gt; (token ? p.resolve(token) : p.reject(error)));
  failedQueue = [];
};</p>
<pre><code>- 큐에 쌓인 B, C 요청들의 resolve(newToken) 실행
- 내부에서:
    - Authorization 헤더를 새 토큰으로 교체
    - api(originalRequest) 실행 → B 재요청

## 전체 흐름 요약</code></pre><ol>
<li><p>A 요청 → 401</p>
</li>
<li><p>isRefreshing = true</p>
</li>
<li><p>/token/access 호출</p>
<p>(그 사이)</p>
</li>
<li><p>B 요청 → 401</p>
</li>
<li><p>failedQueue에 B의 재요청 함수 push</p>
</li>
<li><p>B는 Promise pending 상태로 대기</p>
<p>(refresh 성공)</p>
</li>
<li><p>새 토큰 저장</p>
</li>
<li><p>processQueue 실행
→ B의 resolve(newToken) 실행
→ B 재요청 시작</p>
</li>
<li><p>A도 자기 자신 재요청</p>
</li>
<li><p>isRefreshing = false
```</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 개념 정리]]></title>
            <link>https://velog.io/@gayeong__0916/CICD-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@gayeong__0916/CICD-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 12 Jan 2026 01:23:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-내가-직접-하던-일ci-없을-때">1. 내가 직접 하던 일(CI 없을 때)</h2>
<pre><code>1. 코드 작성
2. git push
3. 로컬에서 npm run lint
4. 로컬에서 npm run build
5. 문제 없다고 판단되면 merge
6. 서버 접속해서 직접 배포</code></pre><h2 id="2-ci를-쓰면-뭐가-달라질까">2. CI를 쓰면 뭐가 달라질까?</h2>
<ul>
<li>CI는 이 코드가 merge 되어도 괜찮은지 자동으로 검사하는 역할</li>
<li>lint / test / build를 자동으로 실행</li>
<li>하나라도 실패하면 merge 불가</li>
<li>사람이 빼먹을 수 없음</li>
<li>merge 가능 여부만!!!! 판단</li>
</ul>
<h2 id="3-push만-했는데-자동으로-실행되는-이유">3. push만 했는데 자동으로 실행되는 이유</h2>
<ul>
<li>.gitlab-ci.yml 파일에 실행 규칙이 정의돼 있기 때문<pre><code>lint:
script:
  - npm run lint
rules:
  - if: $CI_COMMIT_BRANCH</code></pre></li>
<li>코드의 뜻<ul>
<li>브랜치에 push가 발생하면 npm run lint를 자동으로 실행해라<h2 id="cd는">CD는?</h2>
</li>
</ul>
</li>
<li>CI를 통과한 코드를 실제 서버에 배포하는 단계</li>
<li>수동 배포여도 CD임<pre><code>CI: 합쳐도 되는지 검사
CD: 서버에 올려주는 과정</code></pre></li>
<li>중요한 기준<pre><code>배포 명령을 사람이 직접 서버에서 치느냐, CI/CD 파이프라인이 대신 치느냐</code></pre></li>
<li>CI에서 버튼 눌러 배포 -&gt; CD</li>
<li>CI 통과 후 자동 배포 -&gt; CD</li>
<li>사람은 시작만 하고, 실제 배포 작업은 파이프라인이 하면 CD!!!</li>
<li>배포 명령을 CI/CD 파이프라인이 실행하느냐가 중요<h2 id="전체-흐름">전체 흐름</h2>
```</li>
</ul>
<ol>
<li>git push</li>
<li>CI 자동 실행<ul>
<li>lint</li>
<li>test</li>
<li>build</li>
</ul>
</li>
<li>CI 결과로 merge 가능 여부 결정</li>
<li>사람이 merge 버튼 클릭</li>
<li>(선택)<ul>
<li>CD 자동 배포</li>
<li>또는 배포 버튼 클릭 (manual)<pre><code></code></pre></li>
</ul>
</li>
</ol>
<h2 id="gitlab-ciyml-예시">.gitlab-ci.yml 예시</h2>
<pre><code># 1️⃣ 파이프라인 단계 정의 (실행 순서)
stages:
  - lint
  - test
  - build
  - deploy

# 2️⃣ 기본 실행 환경
default:
  image: node:20-alpine
  before_script:
    - node -v
    - npm -v
    - npm ci

# 3️⃣ CI - 코드 검사 (lint)
lint:
  stage: lint
  script:
    - npm run lint
  rules:
    - if: $CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;
    - if: $CI_COMMIT_BRANCH

# 4️⃣ CI - 테스트
test:
  stage: test
  script:
    - npm run test --if-present
  rules:
    - if: $CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;
    - if: $CI_COMMIT_BRANCH

# 5️⃣ CI - 빌드 검증
build:
  stage: build
  script:
    - npm run build
  rules:
    - if: $CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;
    - if: $CI_COMMIT_BRANCH

# 6️⃣ CD - 배포 (main 브랜치만)
deploy:
  stage: deploy
  script:
    - echo &quot;🚀 deploy to server&quot;
    # 예: ssh 서버접속, pm2 reload 등
  rules:
    - if: &#39;$CI_COMMIT_BRANCH == &quot;main&quot;&#39;
      when: manual
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2025년 - 취준생에서 스타트업 직원이 되기까지]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%9A%8C%EA%B3%A0-2025%EB%85%84-%EC%B7%A8%EC%A4%80%EC%83%9D%EC%97%90%EC%84%9C-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85-%EC%A7%81%EC%9B%90%EC%9D%B4-%EB%90%98%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@gayeong__0916/%ED%9A%8C%EA%B3%A0-2025%EB%85%84-%EC%B7%A8%EC%A4%80%EC%83%9D%EC%97%90%EC%84%9C-%EC%8A%A4%ED%83%80%ED%8A%B8%EC%97%85-%EC%A7%81%EC%9B%90%EC%9D%B4-%EB%90%98%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Wed, 31 Dec 2025 04:05:43 GMT</pubDate>
            <description><![CDATA[<h2 id="고통-끝에-낙이-온다는-말처럼">고통 끝에 낙이 온다는 말처럼</h2>
<p>2025년을 한 줄로 요약하자면,
고통 끝에 낙이 온다는 사자성어가 가장 잘 어울리는 해였다.</p>
<p>취업 준비를 다시 시작해야겠다고 마음먹은 순간부터 불안은 자연스럽게 따라왔다.
다시 취준생이 되었고, 어디로 가게 될지 알 수 없는 상태에서 하루하루를 보냈다.</p>
<h2 id="끝이-보이지-않던-취업-준비">끝이 보이지 않던 취업 준비</h2>
<p>서류는 셀 수 없을 정도로 넣었다.
하지만 그중에서 연락이 온 곳은 몇 곳 되지 않았다.</p>
<p>탈락 메일을 받을 때마다 이유를 알 수 없어 답답했고,
어떻게 하면 더 나은 서류가 될 수 있을지 계속해서 고민했다.
주변에 물어보고, 조언을 듣고, 다시 고치는 일을 반복했다.
그 과정은 생각보다 길고 지치기 쉬웠다.</p>
<h2 id="운처럼-찾아온-기회">운처럼 찾아온 기회</h2>
<p>그렇게 흐르던 시간 속에서 면접을 보게 된 회사는 스타트업이었다.
큰 기대를 하지 않으려 했지만, 마음 한편에는 간절함이 자리 잡고 있었다.</p>
<p>그리고 운 좋게도 면접에 합격했다.
그렇게 나는 다시 ‘취준생’이 아닌 ‘회사원’이 되었다.</p>
<h2 id="첫-한-달-실무는-생각보다-달랐다">첫 한 달, 실무는 생각보다 달랐다</h2>
<p>입사한 지 벌써 한 달이 지났다.
회사를 다니면서 가장 크게 느낀 점은
아직 내가 많이 부족하다는 사실이었다.</p>
<p>개발을 시작하기 전에 고민해야 할 것들이 생각보다 많았고,
실무는 단순히 코드를 잘 작성하는 것과는 전혀 다른 영역이라는 걸 깨닫게 되었다.
왜 이 기능이 필요한지, 어떤 흐름에서 사용되는지,
그리고 다른 사람들과 어떻게 약속되어 있는지가 훨씬 중요했다.</p>
<h2 id="부족함을-마주하면서도-얻은-확신">부족함을 마주하면서도 얻은 확신</h2>
<p>회사에 다니며 부족함을 느끼는 순간이 많았지만,
그만큼 이전의 나와는 다른 시선으로 개발을 바라보게 되었다.</p>
<p>예전보다 더 많이 고민하고,
더 깊이 생각하고,
그 이유를 설명하려고 노력하고 있다는 점은 분명했다.</p>
<h2 id="기록으로-남기려는-이유">기록으로 남기려는 이유</h2>
<p>회사에서 알게 된 것들,
실무를 통해 깨달은 점들,
그리고 다시 공부하게 된 내용들을
이 공간에 하나씩 기록해보려고 한다.</p>
<p>2025년이 방향을 찾기 시작한 해였다면,
2026년은 그 방향으로 꾸준히 걸어가며
개발자로서 한 단계 더 성장한 해가 되었으면 한다.</p>
<p>아직 완벽하지는 않지만,
어디로 가고 있는지는 알고 있다는 점에서
이 한 해를 의미 있게 마무리하고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitLab CI/CD]]></title>
            <link>https://velog.io/@gayeong__0916/GitLab-CICD</link>
            <guid>https://velog.io/@gayeong__0916/GitLab-CICD</guid>
            <pubDate>Wed, 31 Dec 2025 03:52:17 GMT</pubDate>
            <description><![CDATA[<p>GitLab CI/CD는 GitLab에 기본으로 내장된 CI/CD 시스템이다.
별도의 CI 툴(Jenkins, CircleCI 등)을 붙이지 않아도,
저장소 하나만 있으면 바로 파이프라인을 구성할 수 있다.
별도 툴 설치 ❌
GitLab 저장소에 바로 연동 ⭕</p>
<blockquote>
<p>GitLab CI/CD를 배우는 게 아니라, CI/CD 파이프라인을 배우는 거고 GitLab은 그걸 구현하는 수단 중 하나다.</p>
</blockquote>
<h2 id="gitlab-cicd의-핵심-구성-요소">GitLab CI/CD의 핵심 구성 요소</h2>
<h3 id="gitlab-ciyml">.gitlab-ci.yml</h3>
<ul>
<li>GitLab CI/CD 파이프라인의 설계도 역할을 한다.<pre><code>stages:
- build
- test
- deploy</code></pre></li>
<li>어떤 작업을</li>
<li>어떤 순서로</li>
<li>언제 실행할지 정의</li>
</ul>
<ul>
<li>이 파일 하나로 CI/CD 동작 전체가 결정된다.<h3 id="job">Job</h3>
</li>
<li>Job은 파이프라인에서 실행되는 가장 작은 작업 단위다.</li>
<li>각 단계에서 실행되는 작업 단위</li>
<li>수행할 작업을 정의<pre><code>test:
stage: test
script:
  - npm install
  - npm test</code></pre></li>
<li>하나의 Job = 하나의 자동화 작업<h3 id="stage">Stage</h3>
</li>
<li>여러 Job들을 묶는 단계</li>
<li>Job들의 묶음</li>
<li>작업을 실행할 시기를 정의<pre><code>build → test → deploy</code></pre></li>
<li>앞 stage 실패 시 다음 stage는 실행 ❌<h3 id="runner">Runner</h3>
</li>
<li>Job을 실제로 실행하는 머신<pre><code>Docker
VM
Shell
Kubernetes 등</code></pre></li>
<li>일 시키는 사람 = GitLab</li>
<li>일 하는 사람 = Runner</li>
<li>GitLab은 “무엇을 할지”만 결정하고, Runner가 실제 명령어(npm run build 등)를 실행한다.<h2 id="gitlab-cicd-동작-흐름">GitLab CI/CD 동작 흐름</h2>
</li>
</ul>
<ol>
<li>개발자가 코드 push</li>
<li>GitLab이 .gitlab-ci.yml 확인</li>
<li>GitLab이 Runner에게 작업 지시</li>
<li>Runner가 실제 작업 실행<ul>
<li>빌드</li>
<li>테스트</li>
<li>배포</li>
</ul>
</li>
<li>결과를 GitLab UI에 표시<h2 id="gitlab-cicd를-쓰는-이유">GitLab CI/CD를 쓰는 이유</h2>
<h3 id="장점">장점</h3>
</li>
</ol>
<ul>
<li>GitLab과 완전 통합</li>
<li>설정 파일 하나로 관리</li>
<li>UI에서 파이프라인 시각화</li>
<li>브랜치 / MR 조건별 실행 가능<h3 id="단점">단점</h3>
</li>
<li>Runner 개념이 처음엔 어려움</li>
<li>YAML 문법 실수 시 디버깅 빡셈</li>
</ul>
<ul>
<li>파이프라인 예시<pre><code>stages:
- install
- build
- deploy
</code></pre></li>
</ul>
<p>// 의존성을 항상 동일한 상태로 설치
install:
  stage: install
  script:
    - npm ci</p>
<p>// 프로젝트가 빌드 가능한 상태인지 검증
// 실제 배포 전에 가장 중요한 단계
build:
  stage: build
  script:
    - npm run build</p>
<p>// main 브랜치에 머지될 때만 실행
// 현재는 배포 동작을 흉내만 내는 상태
// 실제 배포를 하려면 이 부분에 서버 업로드 / 클라우드 배포 명령이 추가되어야 한다.
deploy:
  stage: deploy
  script:
    - echo &quot;Deploying...&quot;
  only:
    - main
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD란?]]></title>
            <link>https://velog.io/@gayeong__0916/CICD%EB%9E%80</link>
            <guid>https://velog.io/@gayeong__0916/CICD%EB%9E%80</guid>
            <pubDate>Wed, 31 Dec 2025 00:28:10 GMT</pubDate>
            <description><![CDATA[<ul>
<li>CI/CD는 코드를 더 자주, 더 안전하게, 더 자동으로 배포하기 위한 개발 문화이자 자동화 파이프라인이다.<h2 id="ci란">CI란?</h2>
</li>
<li>Continuous Integration, 지속적 통합</li>
<li>feature 브랜치에서 작업한 코드를 main에 병합하기 전/후에 자동으로 빌드·테스트해서 병합해도 안전한지 검증하는 과정<h3 id="ci가-없는-경우">CI가 없는 경우</h3>
</li>
<li>각자 로컬에서만 개발</li>
<li>나중에 한 번에 머지</li>
<li>충돌 폭발, 테스트 지옥<h3 id="ci가-있는-경우">CI가 있는 경우</h3>
</li>
<li>코드 push</li>
<li>자동으로 빌드 / 테스트 / 린트 검사</li>
<li>문제 있으면 즉시 실패 알림</li>
<li>머지 시점에 문제가 드러나는 구조<h3 id="ci의-핵심-목적">CI의 핵심 목적</h3>
</li>
<li>버그를 초기에 발견</li>
<li>코드 품질 자동 검증</li>
<li>팀원 간 코드 충돌 최소화<h2 id="cd란">CD란?</h2>
</li>
<li>Continuous Delivery/Deployment, 지속적 제공/배포</li>
<li>CI 이후 단계</li>
<li>배포와 관련된 자동화</li>
</ul>
<h3 id="continuous-delivery">Continuous Delivery</h3>
<ul>
<li>배포 직전까지 자동화</li>
<li>실제 배포는 사람이 버튼 클릭<pre><code>CI 통과 → 배포 준비 완료 → (사람이 승인) → 배포</code></pre></li>
<li>예시<pre><code>deploy:
stage: deploy
script:
  - echo &quot;Ready to deploy&quot;
when: manual
only:
  - main</code></pre></li>
<li>CI는 자동</li>
<li>배포는 버튼 클릭<h3 id="continous-deployment">Continous Deployment</h3>
</li>
<li>CI 통과하면 자동으로 배포까지<pre><code>CI → 테스트 통과 → (자동) 서버 배포</code></pre></li>
<li>예시<pre><code>deploy:
stage: deploy
script:
  - aws s3 sync build/ s3://my-bucket
only:
  - main</code></pre></li>
<li>main 브랜치 머지</li>
<li>CI 통과</li>
<li>즉시 서비스 반영<h3 id="cd의-핵심-목적">CD의 핵심 목적</h3>
</li>
<li>배포 과정의 반복 작업 제거</li>
<li>배포 실수 감소</li>
<li>빠른 피드백 &amp; 빠른 릴리즈</li>
</ul>
<h2 id="cicd-전체-흐름-한줄-요약">CI/CD 전체 흐름 한줄 요약</h2>
<pre><code>코드 Push → 자동 빌드 → 자동 테스트 → 자동 배포</code></pre><p>사람이 하는 일은 : 코드 작성 + 결과 확인 뿐</p>
<p>CI/CD는 코드를 더 자주, 더 안전하게, 더 자동으로 배포하기 위한
개발 문화이자 이를 실현하기 위한 자동화 파이프라인이다.</p>
<p>CI는 코드 변경이 발생할 때마다
자동으로 빌드·테스트를 수행해 품질을 검증하는 단계이고,</p>
<p>CD는 CI를 통과한 코드를
사람의 개입을 최소화해 배포까지 연결하는 단계다.</p>
<p>CI</p>
<ul>
<li>병합해도 괜찮은지?</li>
<li>코드 품질 괜찮은지?</li>
<li>빌드 깨지지 않는지?</li>
</ul>
<p>CD</p>
<ul>
<li>언제 배포할지?</li>
<li>자동 vs 수동?</li>
<li>어디에 배포할지?</li>
</ul>
<h2 id="파이프라인">파이프라인</h2>
<ul>
<li>코드가 배포되기까지 거치는 모든 자동화 단계를 한 줄로 연결한 흐름</li>
<li>자동으로 실행되는 작업들의 흐름</li>
<li>Stage들의 묶음</li>
<li>코드 push 한 번 -&gt; 파이프라인 1개 생성</li>
<li>그 안에 stage들이 순서대로 실행됨<blockquote>
<p>CI/CD는 결국 파이프라인을 설계하고 자동으로 실행하는 시스템이다.</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발을 시작하기 전, 내가 먼저 작성했던 4가지 문서]]></title>
            <link>https://velog.io/@gayeong__0916/%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@gayeong__0916/%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 30 Dec 2025 04:09:19 GMT</pubDate>
            <description><![CDATA[<p>회사를 다니기 전에는 개발이라고 하면 자연스럽게 코드를 먼저 떠올렸다.
하지만 실무에 들어와 보니, 실제 개발은 코드를 작성하기 전 단계에서 이미 절반 이상 결정되고 있었다.</p>
<p>입사 후 한 달 동안 나는 실제 구현에 앞서 다음 4가지 문서를 작성해보았다.</p>
<ul>
<li>유스케이스</li>
<li>플로우 차트</li>
<li>시퀀스 다이어그램</li>
<li>단위 테스트</li>
</ul>
<p>이 문서들을 작성하면서 “왜 개발 전에 정리가 필요한지”를 체감할 수 있었다.</p>
<h2 id="유스케이스-use-case">유스케이스 (Use Case)</h2>
<ul>
<li>사용자의 행동을 기준으로 시스템이 어떻게 반응해야 하는지 정리하는 문서<blockquote>
<p>누가, 어떤 상황에서, 무엇을 하며, 그 결과는 무엇인가?</p>
</blockquote>
</li>
</ul>
<p>라는 질문에 답하는 과정이다.
예를 들어 ‘로그인’이라는 기능 하나만 보더라도</p>
<ul>
<li>로그인 성공</li>
<li>로그인 실패</li>
<li>최초 로그인</li>
<li>조건에 따른 분기</li>
</ul>
<p>처럼 생각보다 많은 경우의 수가 존재한다.</p>
<p>유스케이스를 먼저 작성하면서
기능을 단순히 ‘있다 / 없다’가 아니라, 흐름과 조건으로 바라보게 되었다.</p>
<h2 id="플로우-차트-flow-chart">플로우 차트 (Flow Chart)</h2>
<ul>
<li>유스케이스를 시각적으로 풀어낸 문서</li>
</ul>
<p>텍스트로만 정리했을 때 놓치기 쉬운:</p>
<ul>
<li>조건 분기</li>
<li>반복되는 흐름</li>
<li>예외 케이스</li>
</ul>
<p>를 한눈에 확인할 수 있었다.</p>
<blockquote>
<p>특히, “이 단계에서 실패하면 어디로 돌아가야 하지?” </p>
</blockquote>
<p>같은 질문이 자연스럽게 생겼고, 불필요한 분기나 복잡한 흐름을 미리 정리할 수 있었다.</p>
<h2 id="시퀀스-다이어그램-sequence-diagram">시퀀스 다이어그램 (Sequence Diagram)</h2>
<ul>
<li>시간의 흐름에 따라 시스템 간의 상호작용을 정리하는 문서</li>
</ul>
<ul>
<li>사용자</li>
<li>프론트엔드</li>
<li>서버</li>
<li>외부 시스템</li>
</ul>
<p>이 각각이 어떤 순서로 요청하고 응답하는지를 명확히 표현할 수 있다.</p>
<p>이 문서를 작성하면서</p>
<ul>
<li>이 로직은 프론트 책임인지</li>
<li>서버에서 처리해야 하는지</li>
<li>언제 API가 호출되는지</li>
</ul>
<p>같은 경계가 훨씬 또렷해졌다.</p>
<h2 id="단위-테스트-unit-test">단위 테스트 (Unit Test)</h2>
<ul>
<li>말  그대로 코드의 가장 작은 단위(함수/모듈/컴포넌트)가 의도대로 동작하는지 검증하는 테스트</li>
</ul>
<p>단위 테스트 관점에서 기능을 정리하다 보니</p>
<ul>
<li>정상 케이스</li>
<li>실패 케이스</li>
<li>경계 조건</li>
</ul>
<p>을 자연스럽게 생각하게 되었고,
그 과정에서 </p>
<blockquote>
<p>“이 기능은 이렇게 구현하면 안 되겠구나” </p>
</blockquote>
<p>라는 생각이 구현 전에 먼저 들었다.</p>
<p>단위 테스트가 중요한 이유는 다음과 같다.</p>
<ul>
<li>구현 전에 요구사항의 빈틈이 보인다</li>
<li>코드 변경과 리팩토링이 쉬워진다</li>
<li>버그를 더 빠른 단계에서 발견할 수 있다</li>
</ul>
<p>테스트는 단순한 검증이 아니라, 기능을 정확하게 정의하는 도구라는 느낌을 받았다.</p>
<h2 id="문서를-먼저-쓰며-느낀-점">문서를 먼저 쓰며 느낀 점</h2>
<p>이 네 가지 문서를 작성하며 공통적으로 느낀 점은 다음과 같다.</p>
<ul>
<li>구현 전에 고민할수록</li>
<li>코드 작성 속도는 오히려 빨라진다</li>
<li>질문의 질이 달라진다</li>
<li>“왜 이렇게 구현했는지” 설명할 수 있게 된다</li>
</ul>
<p>문서 작성은 개발의 부수적인 작업이 아니라 개발 사고를 정리하는 핵심 과정이라는 걸 실감했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tailwind v4 vs 이전 버전: 코드 짜며 느낀 변경점만 딱 정리]]></title>
            <link>https://velog.io/@gayeong__0916/Tailwind-v4-vs-%EC%9D%B4%EC%A0%84-%EB%B2%84%EC%A0%84-%EC%BD%94%EB%93%9C-%EC%A7%9C%EB%A9%B0-%EB%8A%90%EB%82%80-%EB%B3%80%EA%B2%BD%EC%A0%90%EB%A7%8C-%EB%94%B1-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@gayeong__0916/Tailwind-v4-vs-%EC%9D%B4%EC%A0%84-%EB%B2%84%EC%A0%84-%EC%BD%94%EB%93%9C-%EC%A7%9C%EB%A9%B0-%EB%8A%90%EB%82%80-%EB%B3%80%EA%B2%BD%EC%A0%90%EB%A7%8C-%EB%94%B1-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 26 Dec 2025 04:41:34 GMT</pubDate>
            <description><![CDATA[<p>Tailwind 4.0으로 넘어오면서 “공식 문서에 있는 차이점”보다, 실제로 코드를 치면서 바로 체감되는 변화가 있었다.
내 경우엔 크게 세 가지였다.</p>
<ol>
<li>파일 삽입 방식이 바뀌었다</li>
<li>커스텀 유틸리티를 @utility로 정의하는 흐름이 생겼다</li>
<li>폰트 커스텀 설정 방식이 바뀌었다.</li>
</ol>
<p><strong>이 글은 내가 프로젝트에서 실제로 적용하면서 느낀 포인트만 기록한다.</strong></p>
<h2 id="파일-삽입-방식">파일 삽입 방식</h2>
<h3 id="v3-기준">v3 기준</h3>
<ol>
<li>tailwind.config.ts를 먼저 만들고<pre><code>npx tailwindcss init</code></pre></li>
<li>config에서 content 경로 설정<pre><code>export default {
content: [
 &quot;./src/**/*.{js,ts,jsx,tsx}&quot;,
],
};</code></pre></li>
<li>CSS 파일에 Tailwind 레이어 선언<pre><code>@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre></li>
</ol>
<h3 id="v4-기준">v4 기준</h3>
<ol>
<li>globals.css에서 바로 Tailwind 불러오기<pre><code>@import &quot;tailwindcss&quot;;</code></pre></li>
<li>config 파일 없이도 시작 가능</li>
</ol>
<ul>
<li>처음엔 tailwind.config.ts 안 만들고 진행</li>
<li>필요한 순간에만 생성</li>
</ul>
<ol start="3">
<li>커스텀 토큰도 CSS에서 관리<pre><code>@theme {
--color-primary-normal: #3acdb9;
--color-text-01: #212121;
}</code></pre><blockquote>
<p>이전엔 Tailwind를 config에서 설정했다면, v4에선 CSS에서 바로 시작했다.</p>
</blockquote>
</li>
</ol>
<h2 id="커스텀-유틸리티-작성-방식">커스텀 유틸리티 작성 방식</h2>
<h3 id="v3-기준-1">v3 기준</h3>
<pre><code>@layer utilities {
  .text-body {
    font-size: 14px;
    line-height: 1.5;
  }
}</code></pre><p>또는</p>
<ul>
<li>plugin 작성</li>
<li>config extend 사용</li>
</ul>
<h4 id="문제점">문제점</h4>
<ul>
<li>이게 유틸인가? 컴포넌트인가? 애매함</li>
<li>점점 CSS가 비대해짐</li>
</ul>
<h3 id="v4-기준-1">v4 기준</h3>
<pre><code>@utility text-body {
  font-size: 14px;
  line-height: 1.5;
}</code></pre><p>사용할 때</p>
<pre><code>&lt;p className=&quot;text-body text-text-02&quot;&gt;
  내용
&lt;/p&gt;</code></pre><ul>
<li>의도가 명확함 -&gt; 이건 유틸이다</li>
<li>Tailwind 클래스랑 같은 레벨로 사용 가능</li>
<li>디자인 토큰 + 유틸 분리하기 쉬움</li>
</ul>
<blockquote>
<p>v4의 @utility는 커스텀 스타일을 Tailwind 유틸로 공식 편입시키는 느낌이었다.</p>
</blockquote>
<h2 id="폰트-삽입-방식">폰트 삽입 방식</h2>
<h3 id="v3-과-v4-차이">v3 과 v4 차이</h3>
<p><a href="https://velog.io/@gayeong__0916/Next.js-App-Router-Tailwind-v4%EC%97%90%EC%84%9C-Pretendard-%EA%B8%B0%EB%B3%B8-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0">https://velog.io/@gayeong__0916/Next.js-App-Router-Tailwind-v4%EC%97%90%EC%84%9C-Pretendard-%EA%B8%B0%EB%B3%B8-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[취업]]></title>
            <link>https://velog.io/@gayeong__0916/%EC%B7%A8%EC%97%85</link>
            <guid>https://velog.io/@gayeong__0916/%EC%B7%A8%EC%97%85</guid>
            <pubDate>Fri, 26 Dec 2025 04:21:25 GMT</pubDate>
            <description><![CDATA[<p>거의 1년만의 취준 끝에 스타트업에 입사를 하게되었다.
다닌지는 벌써 한 달이 다 되어가는데 사람들도 좋고 일도 할만하다</p>
<p>내가 계속 잘 할 수 있을까?
꾸준히 공부를 할 예정이다.
파이팅!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[해결] SVG import 시 ‘모듈을 찾을 수 없습니다’ 오류 해결하기]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%95%B4%EA%B2%B0-SVG-import-%EC%8B%9C-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gayeong__0916/%ED%95%B4%EA%B2%B0-SVG-import-%EC%8B%9C-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 09 Nov 2025 03:30:48 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<ul>
<li>프로젝트에서 SVG 파일을 import할 때 갑자기 발생하는 에러<pre><code>import logo from &quot;@/assets/logo.svg&quot;; 
// ❌ 오류: 모듈 &#39;@/assets/logo.svg&#39; 또는 해당 형식 선언을 찾을 수 없습니다. ts(2307)</code></pre><h2 id="원인">원인</h2>
</li>
<li>분명 잘 되던 코드인데, 어느 날 갑자기????</li>
<li>원인은 : TypeScript가 <code>.svg</code> 파일의 타입을 모르기 때문에</li>
<li>TypeScript는 JS 자산(.js, .ts) 만 타입 정보를 알고 있음</li>
<li>.svg, .png, .jpg, .css 같은 파일은 모듈 선언이 없으면 “이게 뭔 타입인지 몰라서” 컴파일 에러 발생</li>
<li>예전엔 느슨했지만, moduleResolution: &quot;bundler&quot;로 바뀐 뒤 엄격해짐<pre><code>// tsconfig.json (Next.js 14 이상 기본 설정)
&quot;moduleResolution&quot;: &quot;bundler&quot;</code></pre><h2 id="해결-방법">해결 방법</h2>
</li>
<li>declare module &quot;*.svg&quot; 추가<pre><code>// src/types/global.d.ts
declare module &#39;*.svg&#39; {
const content: string;
export default content;
}</code></pre></li>
<li>그리고 tsconfig.json에 타입 파일 포함<pre><code>&quot;include&quot;: [
&quot;next-env.d.ts&quot;,
&quot;src/types/**/*.d.ts&quot;,
&quot;**/*.ts&quot;,
&quot;**/*.tsx&quot;,
&quot;.next/types/**/*.ts&quot;
]</code></pre>이제 TypeScript는 “.svg 파일은 string으로 취급하자”고 이해함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (9)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-9</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-9</guid>
            <pubDate>Sat, 08 Nov 2025 03:13:50 GMT</pubDate>
            <description><![CDATA[<h1 id="cs분야">CS분야</h1>
<h3 id="11-jwtjson-web-token는-어떻게-동작하나요">11. JWT(Json Web Token)는 어떻게 동작하나요?</h3>
<ul>
<li>JWT는 사용자 인증 정보를 JSON형태로 인코딩한 토큰이며, 서버가 서명을 추가해 위변조를 방지합니다. 로그인 시 서버가 사용자 정보를 바탕으로 JWT를 발급하고, 클라이언트는 이 토큰을 로컬 스토리지나 쿠키에 저장합니다. 이후 요청마다 JWT를 함께 보내면 서버는 서명을 검증해 사용자 신원을 확인합니다. 특징은 세션처럼 서버에 사용자 정보를 저장하지 않고, 토큰 자체에 인증 정보가 담겨있는 무상태 방식이라는 점입니다.<h3 id="12-oauth-인증의-흐름은-어떻게-되나요">12. OAuth 인증의 흐름은 어떻게 되나요?</h3>
</li>
<li>사용자가 소셜 로그인을 요청하면, 클라이언트는 사용자의 브라우저를 인증 서버로 리다이렉트를 합니다. 이후 사용자는 Google이나 Kakao 같은 OAuth 제공자에 로그인하고, 애플리케이션 접근 권한을 허용합니다. 그러면 인증 서버는 클라이언트에게 Authorization Code를 전달합니다. 클라이언트는 이 코드를 백엔드 서버로 전달하고, 백엔드 서버는 인증 서버에 해당 코드를 제출해 Access Token(필요 시 Refresh Token도 함께)을 발급받습니다. 이후 클라이언트는 이 Access Token을 이용해 사용자 정보 API를 호출하여 사용자 데이터를 가져올 수 있습니다.
<img src="https://velog.velcdn.com/images/gayeong__0916/post/2d8e3767-ea58-4e34-907c-5022d9e8b928/image.png" alt=""></li>
</ul>
<h3 id="13-배열과-객체의-차이">13. 배열과 객체의 차이</h3>
<ul>
<li>배열과 객체는 모두 JavaScript의 자료 구조지만 데이터 저장 방식과 사용 목적이 다릅니다. 객체는 키-값 형태로 데이터를 저장하며, 특정 속성에 이름을 붙여 관리할 때 사용합니다. 배열은 순서가 있는 데이터 집합으로, 순서가 있는 리스트 형태의 데이터를 저장할 때 사용합니다. 배열은 반복 처리(map, filter 등)나 목록 데이터를 다룰 때 적합하고, 객체는 개별 속성을 가진 엔티티(유저 정보 등)을 표현할 때 사용합니다.<h3 id="14-stack과-queue의-차이">14. Stack과 Queue의 차이</h3>
</li>
<li>Stack과 Queue는 데이터 저장 및 꺼내는 순서가 다른 자료 구조입니다. Stack은 LIFO(Last In, First Out)구조로 나중에 들어온 데이터가 먼저 나갑니다. Queue는 FIFO(First In, First Out) 구조로 먼저 들어온 데이터가 먼저 나갑니다.<h3 id="15-성능-최적화를-위해-어떤-개선을-해본-경험이-있나요">15. 성능 최적화를 위해 어떤 개선을 해본 경험이 있나요?</h3>
</li>
<li>(OpenMind)성능 최적화를 위해 리렌더링 최소화와 초기 로딩 속도 개선에 중점을 두었습니다. 먼저 useMemo, useCallback, React.memo를 활용해 불필요한 리렌더링을 최소화하고, 컴포넌트가 실제로 변경된 부분만 효율적으로 업데이트되도록 했습니다. 또한 스켈레톤 UI를 적용해 데이터 로딩 중에도 사용자에게 시각적 피드백을 제공하여 체감 성능을 높였습니다. 렌더링 구조 측면에서는 SSR과 CSR을 혼합해, 초기 화면은 서버에서 빠르게 렌더링하고 이후 상호작용은 클라이언트에서 처리하도록 구성했습니다. 즉, 렌더링 최소화 + 사용자 체감 속도 개선 + 서버/클라이언트 병행 최적화를 통해 전반적인 성능을 향상시켰습니다.<h3 id="16-csr만-사용하는-spa의-단점은">16. CSR만 사용하는 SPA의 단점은?</h3>
</li>
<li>CSR만 사용하는 SPA는 사용자가 처음 접속할 때 빈 HTML과 큰 JS 번들을 받아야 해서 초기 로딩 속도가 느릴 수 있고, JavaScript가 실행되기 전까지는 화면이 보이지 않아 초기 체감 UX가 떨어질 수 있습니다. 또한 렌더링이 브라우저에서만 이루어지기 때문에 검색 엔진 최적화(SEO)에 불리하고, JS 에러나 네트워크 환경 문제로 스크립트가 로드되지 않으면 페이지가 제대로 표시되지 않는 위험이 있습니다.<h3 id="17-seo가-무엇인지-설명하고-개선을-위해-어떤-작업을-할-수-있을지-설명해-주세요">17. SEO가 무엇인지 설명하고, 개선을 위해 어떤 작업을 할 수 있을지 설명해 주세요.</h3>
</li>
<li>SEO는 웹사이트가 검색 엔진에 잘 노출되도록 최적화하는 작업을 말합니다. 이를 위해 시맨틱 태그와 구조화된 HTML을 사용해 페이지 의미를 명확하게 표현하고, title, description 같은 메타 태그를 최적화합니다. 또한 SSR/SSG를 적용해 검색 크롤러가 실제 콘텐츠를 바로 읽을 수 있게 하고, 이미지에 alt 속성을 추가해 접근성과 검색 인덱싱 효율을 높입니다.<h3 id="18-세션-기반-인증과-토큰-기반-인증을-비교해서-설명해-주세요">18. 세션 기반 인증과 토큰 기반 인증을 비교해서 설명해 주세요.</h3>
</li>
<li>세션 기반 인증과 토큰 기반 인증은 사용자의 로그인 상태를 유지하는 방식이 다릅니다. 세션 기반 인증은 사용자가 로그인하면 서버가 세션을 생성하고, 세션 ID를 쿠키로 클라이언트에 전달합니다. 이후 요청마다 쿠키를 전송하면 서버가 세션 저장소에서 상태를 조회해 인증을 처리합니다. 구현이 단순하고 보안적으로 안정적이지만, 세션 정보를 서버가 관리하기 때문에 서버 부하가 커지고 수평 확장에 불리한 단점이 있습니다. 반면 토큰 기반 인증은 로그인시 서버가 사용자 정보를 담은 토큰을 발급하고, 클라이언트는 이 토큰을 로컬스토리지나 쿠키에 저장합니다. 이후 요청마다 헤더에 토큰을 담아 보내면 서버는 별도의 세션 저장소 없이 토큰의 유효성만 검증합니다. 서버는 별도 상태 저장 없이 토큰의 유효성만 검증하므로 무상태 방식이라 확장성과 모바일/다중 서비스 환경에 유리합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (8)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-8</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-8</guid>
            <pubDate>Fri, 07 Nov 2025 02:57:38 GMT</pubDate>
            <description><![CDATA[<h1 id="cs분야">CS분야</h1>
<h3 id="1-브라우저의-렌더링-과정은-어떻게-되나요">1. 브라우저의 렌더링 과정은 어떻게 되나요?</h3>
<ul>
<li>순서를 간단히 말하면 HTML/CSS파싱 -&gt; 렌더 트리 생성 -&gt; 레이아웃 -&gt; 페인팅 -&gt; 합성 순서로 진해됩니다. 먼저, 브라우저는 서버에서 받은 HTML을 파싱해서 DOM 트리를 만들고, CSS를 파싱해 CSSOM트리를 생성합니다. 이를 결합해 화면에 표시될 요소만 포함한 렌더 트리를 구성하비다. 이후 레이아웃 단계에서 각 요소의 크기와 위치를 계산하고, 페인팅 단계에서 계산된 정보를 기반으로 색상, 그림자, 이미지 같은 시각적 스타일을 픽셀 단위로 그립니다. 마지막으로 합성 단계에서 여러 레이어를 GPU를 이용해 하나의 화면으로 조합합니다. 참고로, JavaScript가 DOM이나 CSSOM을 변경하면, 브라우저는 필요에 따라 다시 계산하는 리플로우 또는 리페인트를 수행합니다. <h3 id="2-http와-https의-차이는-무엇인가요">2. HTTP와 HTTPS의 차이는 무엇인가요?</h3>
</li>
<li>HTTP와 HTTPS는 모두 웹에서 데이터를 주고받는 프로토콜이지만, 보안 측면에서 큰 차이가 있습니다. HTTP는 데이터를 암호화하지 않고 전송하기 때문에 중간에서 데이터를 가로채거나 변조할 위험이 있습니다. 반면 HTTPS는 SSL/TLS 암호화 방식을 사용해 데이터를 암호화하고 서버의 신원을 인증하며 데이터가 중간에 변경되지 않았음을 보장하는 무결성을 제공합니다. 즉, HTTPS는 HTTP에 보안 계층이 추가된 방식으로 로그인 정보, 결제 정보처럼 민감한 데이터를 안전하게 전송할 수 있습니다. <h3 id="3-http-메서드get-post-put-patch-delete의-차이점은">3. HTTP 메서드(GET, POST, PUT, PATCH, DELETE)의 차이점은?</h3>
</li>
<li>GET은 서버에서 데이터를 조회하는데 사용되며, 요청 데이터는 URL에 포함됩니다. 서버의 상태를 변경하지 않는 안전한 메서드입니다. POST는 서버에 새로운 데이터를 생성하거나 처리 요청을 보낼 때 사용됩니다. 요청 본문에 데이터를 담고, 서버 상태가 변경됩니다. PUT은 리소스를 전체를 덮어쓰기 방식으로 업데이트합니다. 대상 리소스가 없으면 생성하기도 합니다. PATCH는 리소스의 일부만 수정할 때 사용합니다. PUT과 달리 변경할 속성만 전송합니다. DELETE는 리소스를 삭제할 때 사용합니다.<h3 id="4-쿠키cookie-세션session-로컬스토리지localstorage-세션스토리지session-storage의-차이점은">4. 쿠키(Cookie), 세션(Session), 로컬스토리지(LocalStorage), 세션스토리지(Session Storage)의 차이점은?</h3>
</li>
<li>쿠키, 세션, 로컬스토리지는 모두 브라우저에 데이터를 저장하는 방법이지만 저장 위치, 전송 방식, 유지 기간, 보안 목적이 다릅니다. 먼저, 쿠키는 브라우저에 저장되며, HTTP 요청마다 자동으로 서버에 전송되는 작은 데이터입니다. 주로 세션 ID나 인증 정보 저장에 사용되며, 용량이 작고 보안 설정(HttpOnly, Secure, SameSite)이 중요합니다. 세션은 데이터가 서버에 저장되고, 브라우저에는 세션 ID만 쿠키로 보관합니다. 서버에서 관리하기 때문에 쿠키보다 보안성이 높지만,서버 메모리/스토리지 사용량이 증가합니다. 로컬 스토리지는 브라우저에 영구 저장되며 탭을 닫아도 유지됩니다. 서버로 자동 전송되지 않기 때문에 사용자 환경 정보, UI 상태, 캐시 데이터 저장에 적합합니다. 반면 보안 목적의 토큰 저장은 권장되지 않습니다. 세션스토리지는 같은 브라우저 탭에서만 유지되며, 탭을 닫으면 제거됩니다. 페이지 이동 중 필요한 임시 데이터에 사용됩니다.<h3 id="5-cors는-무엇이고-왜-발생하나요">5. CORS는 무엇이고, 왜 발생하나요?</h3>
</li>
<li>CORS는 다른 출처의 리소스 요청을 제어하기 위한 브라우저 보안 정책입니다. 브라우저는 보안을 위해 기본적으로 같은 출처에서만 요청을 허용하는 동일 출처 정책을 가지고 있습니다. 그래서 프론트엔드와 API 서버가 도메인, 포트, 또는 프로토콜이 다르면 브라우저가 보안상 요청을 차단하게 되는데 이러한 상황을 CORS 에러라고 부릅니다. CORS 문제를 해결하려면 서버에서 Access-Control-Aloow-Origin 헤더 등을 통해 허용할 출처를 명시하여 브라우저가 요청을 허용하도록 해야 합니다.<h3 id="6-rest-api란-무엇인가요">6. REST API란 무엇인가요?</h3>
</li>
<li>REST 아키텍처 스타일을 기반으로 리소스를 URL로 표현하고, HTTP 메서드를 통해 해당 리소스에 대한 CRUD 동작을 정의하는 API 설계 방식입니다. REST의 핵심은 리소스 중심이며 각 자원은 고유한 URL로 식별되고 GET, POST, PUT, PATCH, DELETE 같은 표준 HTTP 메서드를 사용해 각각 조회, 생성, 수정, 삭제를 수행합니다. REST API는 서버가 이전 요청의 상태를 기억하지 않는 무상태성을 가지며 서버와 클라이언트가 명확히 분리되어 있고 캐싱이 가능해 확장성과 성능이 좋습니다. <h3 id="7-브라우저의-캐시cache는-어떻게-동작하나요">7. 브라우저의 캐시(Cache)는 어떻게 동작하나요?</h3>
</li>
<li>브라우저 캐시는 이전에 받은 리소스를 저장해두었다가, 다음 요청 시 서버에 다시 요청하지 않고 로컬에서 불러오는 방식입니다. 이를 통해 네트워크 비용과 로딩 시간을 줄여 성능을 개선합니다.<h3 id="8-이미지나-리소스-최적화-방법은">8. 이미지나 리소스 최적화 방법은?</h3>
</li>
<li>이미지나 리소스 최적화는 파일 크기와 네트워크 전송량을 줄여 로딩 속도를 개선하는 기법입니다. 주로 WebP 같은 최적화된 이미지 포맷 사용,
화면에 보일 때만 로드하는 Lazy Loading 적용, 이미지 크기 리사이징,
그리고 웹폰트 최적화(예: font-display: swap) 등을 활용합니다.<h3 id="9-코드-스플리팅code-splitting과-lazy-loading은-무엇인가요">9. 코드 스플리팅(Code Splitting)과 Lazy Loading은 무엇인가요?</h3>
</li>
<li>코드 스플리팅은 번들을 여러 조각으로 나누고 필요한 순간에만 로드하여 초기 번들 크기를 줄이는 기법입니다. Lazy Loading은 특정 컴포넌트나 리소스를 사용자에게 필요할 때 가져오는 방식으로,코드 스플리팅을 구현하는 대표 방법입니다.<h3 id="10-렌더링-성능을-개선하는-방법은">10. 렌더링 성능을 개선하는 방법은?</h3>
</li>
<li>렌더링 성능을 개선한다는 것은 불필요한 연산과 리렌더링을 줄여 브라우저의 작업 부담을 낮추는 것입니다. React에서는 React.memo, useMemo, useCallback 등을 사용해 불필요한 컴포넌트 렌더링을 최소화하고, 상태를 적절히 분리해 렌더링 범위를 줄이는 것이 효과적입니다. 브라우저 레벨에서는 DOM 변경을 최소화하고, 레이아웃(Reflow) 비용이 큰 작업을 줄이며, 애니메이션은 transform과 opacity 중심으로 처리해 페인트 비용을 줄이는 방식이 사용됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (7)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-7</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-7</guid>
            <pubDate>Thu, 06 Nov 2025 03:11:57 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs">Next.js</h1>
<h3 id="1-react만-사용할-때와-비교해-nextjs를-사용하는-이유에-대해-설명해주세요">1. React만 사용할 때와 비교해 Next.js를 사용하는 이유에 대해 설명해주세요.</h3>
<ul>
<li>Next.js는 React에 없는 기능을 프레임워크 수준에서 제공하여
운영성과 성능이 뛰어난 웹 애플리케이션을 빠르게 구축할 수 있게 해줍니다. React는 UI 라이브러리이기 때문에 라우팅, 서버 렌더링, 데이터 패칭, 최적화 같은 기능을 직접 설정해야 하지만, Next.js는 CSR 뿐만 아니라 SSR/SSG/ISR를 지원해 초기 렌더링 속도와 SEO를 개선할 수 있습니다. 또한 파일 기반 라우팅, 이미지 최적화, 서버 컴포넌트, API 라우트 등도 기본으로 제공됩니다. 즉, Next.js는 React를 확장해 서버 렌더링, 라우팅, 성능 최적화까지 포함한 올인원 웹 프레임워크입니다. </li>
</ul>
<h3 id="2-sever-component와-client-component의-차이점은-무엇인가요">2. Sever Component와 Client Component의 차이점은 무엇인가요?</h3>
<ul>
<li>서버 컴포넌트는 서버에서 렌더링되는 컴포넌트로, 서버 환경에서 직접 데이터 패칭이 가능하고 번들 크기를 줄여 성능을 최적화 할 수 있습니다. 
렌더링된 HTML을 클라이언트에 전달하기 때문에 초기 로딩 속도와 SEO에 유리합니다. 다만 브라우저 환경이 아니므로 상태 관리, 이벤트 처리, DOM조작 같은 클라이언트 상호작용은 불가능합니다. 반면에 클라이언트 컴포너트는 브라우저에서 실행되는 컴포넌트로 useState, useEffet 같은 훅을 사용해 상태 관리와 이벤트 처리, DOM 조작이 가능합니다. 그러나 클라이언트 번들에 포함되기 때문에 번들 크기에 영향이 있고 초기 로드 비용이 더 큽니다.</li>
</ul>
<h3 id="3-nextjs의-app-router와-pages-router의-차이점을-설명해주세요">3. Next.js의 App Router와 Pages Router의 차이점을 설명해주세요.</h3>
<ul>
<li>Next.js의 App Router와 Pages Router의 가장 큰 차이는 구조와 렌더링 방식, 데이터 패칭 방식입니다. 먼저 파일 구조입니다. Pages Router는 pages/ 디렉토리를 사용하고 파일 기반 라우팅만 제공하지만, App Router는 app/ 디렉토리와 레이아웃 기반 라우팅 및 중첩 레이아웃을 지원해 페이지 공통 UI 구성이 더 쉽습니다. 두번째는 렌더링 방식입니다. Pages Router는 기본이 클라이언트 컴포넌트이고, 서버렌더링이 필요하면 getServerSideProps 등을 명시해야 합니다. 반면 App Router는 서버 컴포넌트가 기본이며, 필요한 경우에만 &#39;use client&#39;를 선언해 클라이언트 컴포넌트를 사용합니다. 세번째는 데이터 패칭 방식입니다. Pages Router는 getStaticProps, getServerSideProps 같은 특수 함수가 필요하지만, App Router는 서버 컴포넌트 내부에서 직접 fetch할 수 있고
Server Actions로 서버 로직을 처리할 수 있습니다. 정리하자면, Pages Router는 전통적인 CSR 기반 아키텍처이고, App Router는 서버 중심의 현대적 아키텍처로 성능과 확장성이 크게 향상된 방식입니다.</li>
</ul>
<h3 id="4-spa와-mpa-csrssrssgisr이-어떤-기준으로-나뉘나요">4. SPA와 MPA CSR/SSR/SSG/ISR이 어떤 기준으로 나뉘나요?</h3>
<ul>
<li>SPA와 MPA는 애플리케이션 구조 기준, CSR/SSR/SSG/ISR은 렌더링 시점과 위치 기준으로 구분됩니다. 즉, SPA/MPA는 페이지 전환 방식이고, 렌더링 방식들은 HTML을 어디서, 언제 생성하는지에 따라 나뉩니다.<h3 id="5-spa와-mpa-차이-설명해보세요">5. SPA와 MPA 차이 설명해보세요.</h3>
</li>
<li>SPA는 전체 앱이 하나의 HTML로 로드되고, 페이지 이동은 클라이언트 라우팅으로 처리합니다. 화면 전환이 빠르고 사용자 경험이 좋지만, 초기 JS 로드가 크고 SEO 대응이 상대적으로 어렵습니다. MPA는 페이지 이동 시마다 서버에서 새로운 HTML을 가져오는 방식입니다. 초기 로딩와 SEO에 유리하지만, 페이지 전환 시 새로고침이 발생합니다.<h3 id="6-csrssrssgisr-차이-설명해보세요">6. CSR/SSR/SSG/ISR 차이 설명해보세요.</h3>
</li>
<li>CSR은 서버가 기본 HTML과 JS 번들을 보내고, 브라우저가 JS를 실행하여 화면을 렌더링하는 방식입니다. 초기 로딩이 느릴 수 있지만, 한 번 로딩되면 페이지 전환이 빠르고 사용자 경험이 부드러운 것이 특징입니다. SSR은 요청 시 서버가 HTML을 생성해 전달하고, 브라우저는 전달된 HTML에 JS를 하이드레이션합니다. 초기 렌더링이 빠르고 SEO에 유리합니다. SSG는 빌드 시 HTML을 미리 생성해 제공하는 방식입니다. 매우 빠르고 비용 효율적이지만, 데이터 변경 시 재빌드가 필요합니다. ISR은 SSG처럼 정적으로 제공하되,일정 주기마다 백그라운드에서 페이지를 다시 생성합니다. 정적 페이지의 성능 + 최신 데이터 유지를 동시에 제공합니다.<h3 id="7-hydration에-대해-설명해-주세요">7. Hydration에 대해 설명해 주세요.</h3>
</li>
<li>Hydration은 서버에서 미리 렌더링된 HTML에 브라우저가 JS를 연결해 인터랙션이 가능하도록 만드는 과정입니다. 즉, SSR이나 SSG로 만든 정적인 HTML을 살려서 상태 관리, 이벤트 처리 같은 기능을 붙여 동적으로 동작하도록 만드는 단계입니다. <h3 id="8-getstaticprops와-getserversideprops의-차이점은-무엇인가요">8. getStaticProps와 getServerSideProps의 차이점은 무엇인가요?</h3>
</li>
<li>둘 다 Next.js에서 Next.js에서 데이터를 가져오는 방식이지만, 실행 시점과 렌더링 방식이 다릅니다. getStaticProps는 빌드 시점에 실행되어 정적 HTML 파일을 미리 생성하는 SSG 방식입니다. 요청 시마다 서버 연산이 필요 없기 때문에 응답 속도가 매우 빠르고 CDN 캐싱이 가능합니다.
다만, 빌드 이후 데이터가 바뀌면 반영이 어려워 자주 변경되지 않는 콘텐츠에 적합합니다. getServerSideProps는 요청 시마다 서버에서 실행되는 SSR 방식입니다. 매 요청마다 최신 데이터를 가져올 수 있어 동적 데이터에 적합하지만, 서버 계산이 필요하기 때문에 응답 속도가 느릴 수 있고 서버 부하가 증가할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (6)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-6</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-6</guid>
            <pubDate>Wed, 05 Nov 2025 03:05:08 GMT</pubDate>
            <description><![CDATA[<h1 id="typescript">TypeScript</h1>
<h3 id="1-javascript만-사용하는-것과-비교해-typescript를-사용하는-이유에-대해-설명해주세요">1. JavaScript만 사용하는 것과 비교해 TypeScript를 사용하는 이유에 대해 설명해주세요.</h3>
<ul>
<li>TypeScript를 사용하는 이유는 정적 타입 검사를 통해 코드의 안정성과 유지보수성을 높일 수 있기 때문입니다. JavaScript는 동적 타입 언어이기 때문에 타입 오류가 실행 중에 발견될 수 있지만, TypeScript는 컴파일 단계에서 타입을 체크해 런타임 오류를 사전에 방지할 수 있습니다. 또한 자동완성, 타입 추론, 인터페이스, 제네릭 같은 기능을 제공해 협업과 대규모 프로젝트에서 코드 품질과 생산성을 크게 높여줍니다. 즉, TypeScript는 JavaScript의 유연함을 유지하면서도 더 안전하고 예측 가능한 개발 환경을 만들어주는 언어입니다.</li>
</ul>
<h3 id="2-typescript의-동작-원리에-대해-설명해주세요">2. TypeScript의 동작 원리에 대해 설명해주세요.</h3>
<ul>
<li>TypeScript는 코드 실행 전에 컴파일 단계에서 타입을 정적으로 검사합니다. 변수나 함수, 객체 구조가 타입에 맞게 사용되었는지 확인하고, 문제가 있으면 컴파일 시점에 오류를 알려줍니다. 이후 TypeScript 컴파일러(tsc)가 타입 정보를 모두 제거하고 순수 JavaScript 코드로 변환합니다.
즉, 타입은 개발 단계에서만 존재하고, 실제 실행은 브라우저나 Node.js에서 JavaScript로 이루어집니다. 이 과정 덕분에 런타임 오류를 사전에 방지할 수 있고, 타입 추론 및 도구 지원을 통해 개발 생산성과 유지보수성이 향상됩니다.</li>
</ul>
<h3 id="3-typescript를-사용했을-때-얻을-수-있는-장점은-무엇인가요">3. TypeScript를 사용했을 때 얻을 수 있는 장점은 무엇인가요?</h3>
<ul>
<li>TypeScript를 사용하면 첫째, 정적 타입 검사를 통해 런타임 에러를 미리 방지할 수 있습니다. 코드를 실행하기 전에 타입 오류를 잡아내기 때문에 안정성이 높습니다. 두번쨰, 자동완성과 타입 추론 덕분에 개발자가 의도한 데이터 구조를 명확히 파악할 수 있어 개발 효율이 올라갑니다. 세번째, 리팩토링과 협업이 쉬워집니다. 타입 정의 덕분에 코드 변경시 영향 범위를 쉽게 파악할 수 있고 다른 개발자들과의 협업 시 의사소통 비용이 줄어듭니다. 즉 TypeScript는 안정성, 유지보수성, 생산성을 모두 향상시켜주는 도구입니다.</li>
</ul>
<h3 id="4-typescript의-인터페이스와-타입-별칭의-차이점은-무엇인가요">4. TypeScript의 인터페이스와 타입 별칭의 차이점은 무엇인가요?</h3>
<ul>
<li>인터페이스와 타입 별칭은 모두 객체의 구조나 타입을 정의할 때 사용되지만 확장서과 표현 범위에서 차이가 있습니다. 먼저 인터페이스는 주로 객체의 구조를 정의하고 확장하기 위한 용도로 사용됩니다. 여러 인터페이스를 extends로 상속할 수 있고, 동일한 이름으로 여러번 선언해도 자동으로 병이 가능합니다. 반면 타입 별치은 객체 뿐 아니라 유니언, 인터섹션, 기본 타입 등 더 다양한 타입 표현을 지원합니다. 하지만 이름이 같은 type을 중복 선언할 수 없고 인터페이스처럼 선언 병합은 불가능합니다. 즉 인터페이스는 객체의 구조를 정의하고 확장할 때 적합하고 타입 별칭은 여러 타입을 조합하거나 복잡한 타입 표현이 필요할 때 적합합니다.</li>
</ul>
<h3 id="5-제네릭을-사용해야하는-이유를-설명해주세요">5. 제네릭을 사용해야하는 이유를 설명해주세요.</h3>
<ul>
<li>제네릭을 사용하는 이유는 유연하면서도 타입 안전한 코드를 재사용하기 위해서입니다. 일반적으로 함수를 만들 때 타입을 고정하면 특정 타입에만 사용할 수 있고, any를 사용하면 타입 정보가 사라져 타입 안정성이 떨어집니다. 제네릭은 타입을 파라미터처럼 전달받아 여러 타입을 지원하면서도
컴파일 단계에서 타입 체크가 가능해 오류를 예방할 수 있습니다. 즉, 제네릭을 통해 코드 재사용성, 유연성, 타입 안정성을 모두 확보할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (5)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-5</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-5</guid>
            <pubDate>Tue, 04 Nov 2025 04:56:22 GMT</pubDate>
            <description><![CDATA[<h1 id="react">React</h1>
<h3 id="11-usememo--usecallback--usecontext--reactmemo">11. useMemo &amp; useCallback &amp; useContext &amp; React.memo</h3>
<ul>
<li>useMemo는 값을 메모이제이션해서 연산 비용이 큰 계산을 캐싱하는 훅입니다. 의존성 배열이 바뀌지 않으면 이전 값을 재사용해 불필요한 계산을 줄입니다. useCallback은 함수를 메모이제이션하는 훅입니다.
렌더링마다 새 함수가 생성되면 자식 컴포넌트가 불필요하게 리렌더링될 수 있기 때문에, 의존성이 변경되지 않는 한 같은 함수 참조를 유지해 React.memo와 함께 사용할 때 유용합니다. React.memo는 props가 변경되지 않으면 컴포넌트를 리렌더링하지 않도록 메모이제이션하는 고차 컴포넌트입니다. useCallback과 함께 사용하면 렌더링 최적화 효과가 큽니다.
useContext는 Context API와 함께 사용되며, props drilling 없이 전역 데이터를 하위 컴포넌트에 전달할 수 있게 합니다. 다만 context 값이 바뀌면 해당 context를 사용하는 모든 컴포넌트가 리렌더링되기 때문에 주의가 필요합니다.</li>
</ul>
<h3 id="12-prop-drilling을-어떻게-해결할-수-있나요">12. Prop drilling을 어떻게 해결할 수 있나요?</h3>
<ul>
<li>Prop drilling은 부모 컴포넌트에서 자식 컴포넌트로, 그리고 그 하위 컴포넌트로 계속 props를 전달해야 하는 상황을 말합니다. 규모가 커질수록 중간 컴포넌트가 불필요한 props를 전달해야 해서 코드가 복잡해지고 유지보수성이 떨어집니다. 이를 해결하기 위해 Context API를 사용하면, 필요한 컴포넌트가 전역 상태에 직접 접근할 수 있어 중간 컴포넌트를 거치지 않고 데이터를 전달할 수 있습니다. 다만 Context도 값이 변경되면 해당 Context를 구독하는 컴포넌트들이 모두 리렌더링되기 때문에, 규모가 더 커지면 Zustand, Redux 같은 상태 관리 라이브러리를 사용해 최적화할 수 있습니다.</li>
</ul>
<h3 id="13-react-query가-만들어진-이유와-사용할-때-얻게-되는-이점에-대해-설명해주세요">13. React Query가 만들어진 이유와 사용할 때 얻게 되는 이점에 대해 설명해주세요.</h3>
<ul>
<li>React Query는 서버에서 가져온 데이터를 효율적으로 관리하기 위한 라이브러리입니다. 기존에는 useEffect와 useState로 로딩, 에러 처리, 캐싱 등을 직접 관리해야 해서 코드가 복잡해졌는데, React Query는 이 과정을 자동화하고, 캐싱과 리패칭을 통해 최신 데이터를 유지하면서 네트워크 요청도 최소화합니다. 그래서 서버 상태 관리에 React Query를 사용하면 코드 유지보수성과 사용자 경험이 모두 향상됩니다.</li>
</ul>
<h3 id="14-react의-상태-관리-방법을-설명하세요">14. React의 상태 관리 방법을 설명하세요.</h3>
<ul>
<li>React의 상태 관리는 크게 로컬 상태, 전역 상태, 서버 상태로 나눌 수 있습니다. 우선 컴포넌트 내부에서만 사용하는 값은 useState나 useReducer로 관리하는 로컬 상태입니다. 여러 컴포넌트에서 공유해야 하는 값은 전역 상태로 분류되며, 이때는 Context API를 사용하거나, 규모가 커질 경우 Redux, Zustand, Recoil 같은 상태 관리 라이브러리를 사용할 수 있습니다.그리고 서버에서 가져온 데이터처럼 외부 비동기 데이터(state)는 React Query 같은 서버 상태 관리 라이브러리를 사용하면, 캐싱이나 동기화, 리패칭이 자동화되어 효율적입니다. 요약하면, React 상태 관리는 로컬 상태 → 전역 상태 → 서버 상태로 구분되며, 상황에 맞는 도구를 선택하는 것이 중요합니다.</li>
</ul>
<h3 id="15-context-api와-zustand의-차이점은">15. Context API와 Zustand의 차이점은?</h3>
<ul>
<li>Context API와 Zustand는 둘 다 전역 상태 관리를 위한 도구이지만 역할과 확장성 측면에서 차이가 있습니다. Context API는 React 내장 기능으로 전역 값을 하위 컴포넌트에 전달하기 위한 전역 데이터 전달 도구입니다. 그러나 상태가 커지거나 값이 자주 바뀌는 경우, Context를 구독하는 컴포너트 전체가 리렌더링되는 문제가 있어 대규모 상태관리에는 비효율적일 수 있습니다. 반면 Zustand는 경량 전역 상태 관리 라이브러리로, 분리된 스토어에서 필요한 상태만 선택적으로 구독할 수 있어 리렌더링이 최소화되고 성능이 좋습니다. 또한 비동기 로직 처리, 미들웨어, persist 저장 등의 기능이 내장되어 있어 Context보다 더 실용적으로 확장성 있는 상태 관리가 가능합니다. 정리하면, Context API는 간단한 전역 데이터 공유에 적합하고, Zustand는 상태가 많거나 자주 업데이트되는 애플리케이션에서 더 유리합니다.</li>
</ul>
<h3 id="16-제어-컴포넌트--비제어-컴포넌트">16. 제어 컴포넌트 &amp; 비제어 컴포넌트</h3>
<ul>
<li>React에서 제어 컴포넌트와 비제어 컴포넌트는 폼 입력값을 어디에서 관리하느냐에 따라 구분됩니다. 제어 컴포넌트는 입력값을 React의 state로 관리하며, onChange를 통해 값이 업데이트됩니다. React가 입력 데이터를 항상 알고 있어 실시간 검증, 조건부 렌더링, 폼 로직 제어가 쉽습니다.
반면 비제어 컴포넌트는 입력값을 DOM이 직접 관리하고, ref를 통해 값에 접근합니다. React가 값을 매 렌더마다 추적하지 않기 때문에 코드가 단순하고 성능도 유리할 수 있지만, 입력 상태 제어와 검증은 상대적으로 어려울 수 있습니다. 따라서 실시간 검증·상태 제어가 필요하면 제어 컴포넌트,
간단한 입력 처리나 초기값만 필요할 때는 비제어 컴포넌트가 적합합니다.</li>
</ul>
<h3 id="17-virtual-dom은-어떤-방식으로-이전-노드와-비교하나요">17. Virtual DOM은 어떤 방식으로 이전 노드와 비교하나요?</h3>
<ul>
<li>React는 Virtual DOM을 비교할 때 Reconciliation 알고리즘을 사용합니다. 기본 원칙은 같은 레벨의 노드끼리 비교하면서 타입이 같으면 props만 비교해 변경된 부분만 업데이트하고 타입이 다르면 해당 노드와 자식 노드를 모두 새로 생성합니다. 리스트의 경우에는 key 값을 기준으로 요소의 재사용 여부를 판단해서 불필요한 재렌더링을 줄입니다. 이런 방식으로 React는 전체 트리를 모두 비교하지 않고 빠르게 변경 부분만 업데이트합니다.</li>
</ul>
<h3 id="18-react에서-비동기-요청-처리-시-로딩-에러-데이터-상태를-어떻게-관리하나요">18. React에서 비동기 요청 처리 시 로딩, 에러, 데이터 상태를 어떻게 관리하나요?</h3>
<ul>
<li>React에서 비동기 요청을 처리할 때는 주로 useEffect와 useState를 사용해 로딩 상태, 에러 상태, 데이터 상태를 각각 관리합니다. 요청 시작 시 loading을 true로 설정하고, 성공 시 데이터를 저장하고, 실패 시 error 상태를 업데이트합니다. 다만 이 방식은 로딩/에러 처리, 취소, 캐싱 등을 직접 관리해야 해 코드가 복잡해질 수 있습니다. 그래서 실무에서는 React Query 같은 서버 상태 관리 라이브러리를 사용해 로딩, 에러, 캐싱, 리패칭 등을 자동으로 처리해 효율적으로 관리합니다.</li>
</ul>
<h3 id="19-전역-상태와-서버-상태의-차이를-설명해주세요">19. 전역 상태와 서버 상태의 차이를 설명해주세요.</h3>
<ul>
<li>전역 상태와 서버 상태의 차이는 데이터의 출처와 관리 방식에 있습니다.
전역 상태는 애플리케이션 내부에서만 존재하는 값으로, 로그인 여부, 모달 여부, UI 상태처럼 클라이언트에서만 관리되는 데이터입니다. 이런 경우 Redux, Zustand, Recoil 같은 전역 상태 관리 도구를 사용할 수 있습니다. 반면 서버 상태는 서버에서 가져온 데이터이며, 데이터가 서버에서 변경될 수 있기 때문에 캐싱, 리패칭, 동기화가 필요합니다. 이를 효율적으로 처리하기 위해 React Query(TanStack Query) 같은 서버 상태 관리 라이브러리를 사용합니다. 즉, 전역 상태는 클라이언트 중심 데이터, 서버 상태는 외부 데이터와의 신뢰성과 동기화가 필요한 상태입니다.</li>
</ul>
<h3 id="20-클라이언트-상태와-서버-상태의-차이는">20. 클라이언트 상태와 서버 상태의 차이는?</h3>
<ul>
<li>클라이언트 상태는 브라우저 안에서만 존재하고 조작되는 데이터로, 컴포넌트 상태, UI 상태, 폼 입력값 등 앱 내부 로직에 의해 결정됩니다. 서버 상태는 서버에서 가져온 데이터이며, 네트워크 요청이 필요하고 서버 데이터 변경에 따라 최신 상태를 유지해야 합니다. 캐싱, 리패칭, 동기화 전략이 필요하기 때문에 React Query 같은 도구로 관리합니다.</li>
</ul>
<p>클라이언트 상태: 우리 집(브라우저) 안의 모든 물건
전역 상태: 가족 모두가 함께 쓰는 공용 물건 (냉장고, TV)
로컬 상태: 나만 쓰는 개인 물건 (내 방 책상 위의 노트)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (4)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-4</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-4</guid>
            <pubDate>Tue, 04 Nov 2025 04:49:41 GMT</pubDate>
            <description><![CDATA[<h1 id="react">React</h1>
<h3 id="1-react를-왜-쓸까">1. React를 왜 쓸까?</h3>
<ul>
<li>React는 UI를 효율적으로 만들고 관리하기 위한 라이브러리입니다.
기존에는 DOM을 직접 조작해야 해서 코드가 복잡하고 성능이 떨어졌지만, React는 Virtual DOM을 통해 변경된 부분만 효율적으로 업데이트하여 렌더링 성능을 높입니다. 또한 컴포넌트 기반 구조 덕분에 코드의 재사용성과 유지보수성이 뛰어나며, 상태 변화에 따라 UI가 자동으로 갱신되는 선언적인 방식으로 개발할 수 있습니다. 즉, React를 사용하면 복잡한 UI를 구조적으로 관리하면서도 빠르고 일관된 사용자 경험을 제공할 수 있습니다.</li>
</ul>
<h3 id="2-virtual-dom이-무엇인지-이를-사용하는-이유는-무엇인지-설명해주세요">2. Virtual DOM이 무엇인지, 이를 사용하는 이유는 무엇인지 설명해주세요.</h3>
<ul>
<li>Virtual DOM은 실제 DOM을 추상화한 가상 객체 모델로, 브라우저의 DOM조작을작을 최소화해 렌더링 성능을 최적화합니다. React는 상태가 변경되면 새로운 Virtual DOM을 만들고, 이전 Virtual DOM과 비교한 뒤 바뀐 부분만 실제 DOM에 반영합니다. 이런 구조 덕분에 효율적인 업데이트가 가능하고, 복잡한 UI에서도 빠른 렌더링을 유지할 수 있습니다.</li>
</ul>
<h3 id="3-react에서-배열을-렌더링할-때-key를-써야-하는-이유에-대해-설명해주세요">3. React에서 배열을 렌더링할 때 key를 써야 하는 이유에 대해 설명해주세요.</h3>
<ul>
<li>React에서 key는 Virtual Dom의 비교 과정에서 각 요소를 식별하기 위해 사용됩니다. key가 있어야 React에서 어떤 요소가 변경되었는지 효율적으로 비교하고, 불필요한 재렌더링 없이 변경된 부분만 업데이트 할 수 있습니다. key가 없거나 index를 사용할 경우, 요소의 순서가 바뀌면 state가 꼬이거나 예기치 않은 UI 오류가 생길 수 있습니다.</li>
</ul>
<h3 id="4-react-생명주기에-대해-설명해주세요">4. React 생명주기에 대해 설명해주세요.</h3>
<ul>
<li>React 생명주기는 컴포넌트가 생성되고, 업데이트되고, 제거되는 일련의 과정을 말합니다. 마운트 시에는 DOM이 처음 생성되고, 업데이트 시에는 state나 props 변경으로 리렌더링이 일어나며 언마운트 시에는 컴포넌트가화면에서 사라지면서 리소스 정리를 합니다. 함수형에서는 useEffet를 사용해 이 과정을 동일하게 처리할 수 있습니다.</li>
</ul>
<h3 id="5-react의-리렌더링은-언제-발생하나요">5. React의 리렌더링은 언제 발생하나요?</h3>
<ul>
<li>React의 리렌더링은 컴포넌트의 state, props, 또는 context 값이 변경될 때 발생합니다. 또한 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 함께 리렌더링될 수 있습니다. 이런 변경이 발생하면 React는 컴포넌트를 다시 렌더링해 새로운 Virtual DOM을 만들고, 이전 Virtual DOM과 비교(diffing)하여 바뀐 부분만 실제 DOM에 반영합니다. 반면, 값이 동일하게 유지되거나 props가 변하지 않은 경우, React.memo를 사용해 불필요한 리렌더링을 방지할 수 있습니다.</li>
</ul>
<h3 id="6-useeffect의-의존성-배열이-빈-경우와-특정-값이-있는-경우의-차이점을-설명하세요">6. useEffect의 의존성 배열이 빈 경우와 특정 값이 있는 경우의 차이점을 설명하세요.</h3>
<ul>
<li>useEffect의 의존성 배열이 빈 경우에는 컴포넌트가 처음 마운트될 때 한 번만 실행되고, 이후에는 다시 실행되지 않습니다. 반면 특정 값이 있는 경우, 해당 값이 변경될 때마다 useEffect가 재실행됩니다. 마지막으로 의존성 배열을 생략한 경우, 컴포넌트가 리렌더링될 때마다 매번 실행됩니다.</li>
</ul>
<h3 id="7-react-hook을-사용할-때-주의할-점은-무엇인가요">7. React Hook을 사용할 때 주의할 점은 무엇인가요?</h3>
<ul>
<li>React Hook은 호출 순서에 의존하기 때문에 몇 가지 중요한 규칙을 반드시 지켜야 합니다. 첫째, Hook은 컴포넌트의 최상위에서만 호출해야 하며, 조건문이나 반복문, 내부 함수 안에서는 호출할 수 없습니다. 둘째, React 함수 컴포넌트나 커스텀 훅 내부에서만 사용할 수 있습니다. 셋째, useEffect나 useCallback 같은 훅에서는 의존성 배열을 정확하게 관리해야 합니다. 이러한 규칙을 지키지 않으면 React가 상태를 올바르게 추적하지 못해 렌더링 오류나 상태 불일치 문제가 발생할 수 있습니다.</li>
</ul>
<h3 id="8-state를-사용하지-않고-usestate를-이용하는-이유는">8. state를 사용하지 않고 useState를 이용하는 이유는?</h3>
<ul>
<li>React에서 useState를 사용하는 이유는 상태 변화에 따라 컴포넌트를 자동으로 리렌더링하기 위해서입니다. 만약 일반 변수만 사용한다면 값이 바뀌어도 React는 그 변화를 감지하지 못해서 화면이 업데이트되지 않습니다.
반면 useState를 사용하면 React가 해당 값을 상태로 추적하고, 값이 변경될 때마다 자동으로 컴포넌트를 리렌더링해 UI와 데이터가 항상 동기화되도록 유지합니다. 즉, useState는 값을 저장하는 것뿐 아니라 React가 상태 변화를 인식하고 UI를 갱신할 수 있게 해주는 메커니즘입니다.</li>
</ul>
<h3 id="9-상태값state--속성값props">9. 상태값(State) &amp; 속성값(Props)</h3>
<ul>
<li>React에서 Props와 State는 모두 컴포넌트의 데이터를 관리하지만, Props는 부모 컴포넌트가 자식에게 전달하는 읽기 전용 데이터이고, State는 컴포넌트 내부에서 직접 관리하고 변경할 수 있는 상태값입니다. Props는 외부로부터 주어지는 데이터라 수정할 수 없지만, State는 값이 바뀌면 리렌더링을 유발해 컴포넌트의 동적 변화를 처리합니다. 그래서 Props는 전달용 데이터, State는 내부 상태관리용 데이터라고 생각합니다. </li>
</ul>
<h3 id="10-react에서-useeffect와-uselayouteffect의-차이점은-무엇인가요">10. React에서 useEffect와 useLayoutEffect의 차이점은 무엇인가요?</h3>
<ul>
<li>useEffect와 useLayoutEffect는 모두 렌더링 이후 실행되는 훅이지만 실행 시점이 다릅니다. useEffect는 브라우저가 화면을 그린 뒤 비동기적으로 실행되기 때문에 렌더링을 막지 않습니다.
반면, useLayoutEffect는 DOM이 변경된 직후, 브라우저가 화면을 그리기 전에 동기적으로 실행됩니다. 따라서 UI가 눈에 보이기 전에 DOM을 측정하거나 스타일을 조정해야 하는 경우 useLayoutEffect를 사용하고,
일반적인 부수 효과 작업(fetch, 이벤트 등록 등)에는 성능에 유리한 useEffect를 사용합니다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (3)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-3</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-3</guid>
            <pubDate>Fri, 31 Oct 2025 02:56:15 GMT</pubDate>
            <description><![CDATA[<h1 id="javascript">JavaScript</h1>
<h3 id="13-javascript가-다른-언어와-다른-특징-3가지를-서술하세요">13. JavaScript가 다른 언어와 다른 특징 3가지를 서술하세요.</h3>
<ul>
<li>JavaScript는 먼저 인터프리터 기반 언어입니다. 실행 전에 전체 코드를 컴파일하지 않고, 브라우저나 Node.js엔진이 코드를 한줄씩 해석하며 즉시 실행합니다. 두 번째는 동적 타이핑 언어입니다. 변수의 자료형을 미리 지정하지 않고, 실행 시점에 값에 따라 타입이 결정됩니다. 마지막은 싱글 스레드 언어입니다. JavaScript는 싱글 스레드 언어이지만, 이벤트 루프와 비동기처리 메커니즘을 통해 동시에 여러 작업이 수행되는 것처럼 동작합니다.</li>
</ul>
<h3 id="14-javascript에서-클로저란-무엇이고-언제-사용하나요">14. JavaScript에서 클로저란 무엇이고 언제 사용하나요?</h3>
<ul>
<li>JavaScript에서 클로저는 함수가 선언될 때의 스코프(환경)를 기억하고, 그 스코프 밖에서 해당 변수에 접근할 수 있게 하는 기능을 말합니다. 즉, 함수가 실행이 끝나서 스코프가 사라진 뒤에도 내부 함수가 외부 함수의 변수에 계속 접근할 수 있는 현상입니다. 클로저는 주로 상태를 은닉하거나 유지해야 할 때, 그리고 함수형 프로그래밍 스타일로 변수를 캡슐화할 때 사용됩니다.</li>
</ul>
<h3 id="15-프로토타입에-대해-설명해주세요">15. 프로토타입에 대해 설명해주세요.</h3>
<ul>
<li>JavaScript에서 프로토타입은 객체 간의 상속을 구현하기 위한 메커니즘입니다. 모든 객체는 내부적으로 프로토타입이라는 숨겨진 프로퍼티를 가지고 있고, 이는 자신의 부모 역할을 하는 프로토타입 객체를 참조합니다. 객체에서 특정 속성이나 메서드를 찾을 때, 자신에게 없으면 프로토타입 체인을 따라 상위 객체에서 탐색할 수 있습니다.</li>
</ul>
<h3 id="16-프로토타입과-상속-구조는-어떻게-되나요">16. 프로토타입과 상속 구조는 어떻게 되나요?</h3>
<ul>
<li>JavaScript에서 객체는 생성될 때 <code>[[Prototype]]</code>을 통해 상위 객체와 연결되며, 이를 프로토타입 기반 상속이라고 합니다.
예를 들어 생성자 함수로 객체를 만들면, 그 인스턴스의 <code>[[Prototype]]</code>은 Constructor.prototype을 참조합니다. 해당 prototype에는 인스턴스들이 공유하는 메서드와 속성이 정의됩니다.
객체에서 프로퍼티를 조회할 때, 자신에게 없으면 prototype을 따라 상위 객체를 탐색하는데, 이를 프로토타입 체인이라고 합니다. 최종적으로 Object.prototype까지 올라가며, 그곳에도 없으면 undefined를 반환합니다.</li>
</ul>
<h3 id="17-깊은-복사와-얕은-복사의-차이점은-무엇인가요">17. 깊은 복사와 얕은 복사의 차이점은 무엇인가요?</h3>
<ul>
<li>얕은 복사는 객체의 1차 속성까지만 복사하고, 내부에 있는 중첩 객체는 참조를 그대로 복사합니다. 그래서 복사본을 수정하면 원본에도 영향을 줄 수 있습니다. 반면 깊은 복사는 중첩된 객체까지 완전히 새로운 메모리 공간에 복사해서 복사본과 원본이 서로 독립적으로 존재합니다. 예를 들어 spread 연산자나 Object.assign은 얕은 복사, structuredClone이나 JSON.parse(JSON.stringify)는 깊은 복사에 해당합니다.</li>
</ul>
<h3 id="18-function-키워드로-사용하는-일반-함수와-화살표-함수의-차이점은-무엇인가요">18. Function 키워드로 사용하는 일반 함수와 화살표 함수의 차이점은 무엇인가요?</h3>
<ul>
<li>일반 함수와 화살표 함수는 문법뿐 아니라 동작 방식에서도 차이가 있습니다. 첫번째, 일반함수는 this가 동적으로 바인딩되지만, 화살표 함수는 선언된 위치의 상위 스코프 this를 그대로 사용합니다. 두번째는 일반함수는 arguments 객체를 가지지만 화살표 함수는 갖지 않습니다. 마지막으로 일반함수는 생성자 함수로 new 키워드를 사용할 수 있지만, 화살표 함수는 프로토타입이 없어 생성자로 사용할 수 없습니다. </li>
</ul>
<h3 id="19-화살표-함수를-사용했을-때는-new-생성자를-사용해서-새로운-객체-상속등을-할-수-없습니다-그-이유에-대해서-설명해주세요">19. 화살표 함수를 사용했을 때는 new 생성자를 사용해서 새로운 객체, 상속등을 할 수 없습니다. 그 이유에 대해서 설명해주세요.</h3>
<ul>
<li>화살표 함수는 new 키워드로 사용할 수 없습니다. 내부적으로 생성자 호출에 필요한 [[Construct]] 메서드가 없고, 프로토타입 프로퍼티도 존재하지 않기 때문에 상속 체인을 만들 수 없습니다. 또 화살표 함수는 lexical this, 즉 상위 스코프의 this를 그대로 사용하기 때문에 새로운 인스턴스용 this를 생성할 수도 없습니다. 그래서 주로 생성자보다는 콜백이나 짧은 함수 표현식에 사용됩니다. </li>
</ul>
<h3 id="20-배열-메서드">20. 배열 메서드</h3>
<ul>
<li>배열 메서드는 원본을 변경하는 메서드와 변경하지 않는 메서드로 나눌 수 있습니다. 예를 들어 push, splice, sort는 원본 배열을 변경하고, map, filter, slice는 새로운 배열을 반환합니다. forEach는 단순 반복용이고, map은 변환용이며, reduce는 배열을 누적해 하나의 값으로 축약할 때 사용합니다.
<img src="https://velog.velcdn.com/images/gayeong__0916/post/ff8ce386-983d-401f-8a48-e80968fb62a4/image.png" alt="">
<img src="https://velog.velcdn.com/images/gayeong__0916/post/254eecbc-46c1-421b-be97-509d4a65c453/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (2)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-2</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-2</guid>
            <pubDate>Thu, 30 Oct 2025 02:49:00 GMT</pubDate>
            <description><![CDATA[<h1 id="javascript">JavaScript</h1>
<h3 id="1-javascript는-어떤-언어일까">1. JavaScript는 어떤 언어일까?</h3>
<ul>
<li>JavaScript는 웹 브라우저에서 동작하는 인터프리터 기반의 객체지향 스크립트 언어입니다. 웹 페이지에 동적인 기능과 상호작용을 추가하는 역할을 하며, 브라우저 뿐만 아니라 Node.js 환경에서도 서버 개발에 사용되는 범용 언어로 발전했습니다. 또한 JavaScript는 싱글 스레드 언어로, 한 번에 하나의 작업만 처리할 수 있는 구조를 가지고 있습니다. 하지만 이벤트 루프와 비동기 처리 메커니즘을 통해 동시에 여러 작업이 수행되는 것처럼 효율적으로 동작합니다. </li>
</ul>
<h3 id="2-이벤트-버블링-캡쳐링-위임에-대해-설명해-주세요">2. 이벤트 버블링, 캡쳐링, 위임에 대해 설명해 주세요.</h3>
<ul>
<li>이벤트 버블링은 이벤트가 타깃 요소에서 시작해 상위 요소로 전파되는 단계입니다. 즉 자식에서 부모 방향으로 이벤트가 올라갑니다. 이벤트 캡쳐링은 이벤트가 가장 상위 요소에서 시작해 타깃 요소까지 내려가는 단계입니다. 즉, 부모에서 자식 방향으로 이벤트가 전파됩니다. addEvetListener의 세 번째 인자를 true로 설정하면 캡쳐링 단계에서 이벤트를 감지할 수 있습니다. 이벤트 위임은 버블링 특성을 이용해 여러 자식 요소에 각각 이벤트를 붙이지 않고 부모 요소 하나에 이벤트를 등록해서 하위 요소들의 이벤트를 한 번에 처리하는 기법입니다. 주로 동적으로 추가되는 요소나 리스트 항목 관리 등에 사용됩니다. </li>
</ul>
<h3 id="3-this에-대해-설명해주세요">3. this에 대해 설명해주세요</h3>
<ul>
<li>JavaScript에서 this는 함수를 호출한 주체를 참조하는 키워드입니다. 즉, 함수가 어디서 정의됐는지가 아니라 어떻게 호출됐는지에 따라 값이 달라집니다. 전역 컨텍스트에서 브라우저에서는 this가 window 객체를 Node.js에서는 global 객체를 가리킵니다. 일반 함수 호출시 기본적으로 this는 전역 객체(window)를 가리킵니다. 단 &#39;use strict&#39; 모드에서는 undefined가 됩니다. 메서드 호출 시 객체의 메서드 내부에서 this는 그 메서드를 호출한 객체 자신을 가리킵니다. 생성자 함수(new) 호출 시 this 는 새로 생성되는 인스턴스 객체를 가리킵니다. 화살표 함수는 자신만의 this를 가지지 않고 선언된 위치의 상위 스코프의 this를 그대로 참조합니다.(Lexical this)</li>
</ul>
<h3 id="4-var-let-const-를-중복-선언-허용-스코프-호이스팅-관점에서-서로-비교해-주세요">4. var, let, const 를 중복 선언 허용, 스코프, 호이스팅 관점에서 서로 비교해 주세요.</h3>
<ul>
<li>var는 중복 선언이 가능하며 함수 스코프를 가집니다. 블록 안에서 선언해도 함수 전체에서 접근할 수 있습니다. 호이스팅 시 선언만 끌어올려지고, 초기화는 나중에 이루어집니다. 즉, 선언 전에 접근하면 undefined가 출력됩니다. let은 중복 선언은 허용되지 않으며 블록 스코프를 가지기 때문에 블록 내부에서만 유효합니다. 호이스팅은 되지만, TDZ때문에 선언 전에 접근하면 ReferneceError가 발생합니다. const는 중복 선언 불가며 블록 스코프를 가집니다. 선언과 동시에 초기화가 반드시 필요합니다. 호이스팅은 let과 동일하게 일어나지만 TDZ로 인해 선언 전 접근이 불가능합니다. </li>
</ul>
<h3 id="5-이벤트-루프란">5. 이벤트 루프란?</h3>
<ul>
<li>JavaScript는 단일 스레드로 동작하기 때문에 한 번에 하나의 작업만 처리할 수 있습니다. 하지만 비동기 코드를 실행하기 위해 이벤트 루프가 사용됩니다. 이벤트 루프는 콜 스택과 태스크 큐를 감시하면서 스택이 비었을 때 큐에 콜백함수를 꺼내 실행시켜 비동기 작업을 순차적으로 처리합니다.</li>
</ul>
<h3 id="6-비동기-처리란">6. 비동기 처리란?</h3>
<ul>
<li>비동기 처리는 코드가 실행되는 동안 작업이 완료될 때까지 기다리지 않고, 다른 작업을 동시에 진행할 수 있도록 하는 방식입니다. </li>
</ul>
<h3 id="7-콜백함수란">7. 콜백함수란?</h3>
<ul>
<li>콜백함수란 다른 함수의 인자로 전달되어 특정 시점에 실행되는 함수를 의미합니다. 하지만 콜백이 중첩되면 콜백 지옥이 생겨 코드 가독성과 에러 처리가 어려워집니다.</li>
</ul>
<h3 id="8-promise란">8. Promise란?</h3>
<ul>
<li>Promise는 비동기 작업의 상태를 표현하는 객체롤, peding, fulfilled, rejected의 세 가지 상태를 가집니다. then, catch, finally를 사용해 콜백보다 더 깔끔하게 비동기 흐름을 관리할 수 있습니다.</li>
</ul>
<h3 id="9-asyncawait란">9. async/await란?</h3>
<ul>
<li>async/await는 Promise를 더 직관적으로 사용할 수 있게 하는 문법입니다. await 키워드를 사용하면 Promise가 처리 될때까지 기다렸다가 결과를 반환받을 수 있습니다. 코드가 동기식처럼 보이지만, 실제로는 비동기적으로 동작합니다.</li>
</ul>
<h3 id="10-javascript에서-비동기-처리를-하는-event-loop의-동작-원리">10. JavaScript에서 비동기 처리를 하는 Event Loop의 동작 원리</h3>
<ul>
<li>JavaScript는 단일 스레드 언어라서 한 번에 하나의 작업만 처리합니다.  그럼에도 비동기 작업을 처리할 수 있는 이유는 브라우저(Web API)와 이벤트 루프(Event Loop) 구조 덕분입니다.
자바스크립트 코드가 실행되면 함수들은 콜 스택(Call Stack)에 쌓여 순서대로 실행됩니다. 동기 코드는 즉시 실행되지만, setTimeout, fetch 같은 비동기 작업은 콜 스택에서 Web API 영역으로 넘겨져 별도로 처리됩니다.
비동기 작업이 완료되면 해당 콜백 또는 resolve 함수는 Task Queue 또는 Microtask Queue로 이동합니다.
이벤트 루프는 콜 스택이 비었는지 계속 확인하다가, 스택이 비면 큐에서 대기 중인 작업을 스택으로 옮겨 실행합니다.</li>
</ul>
<h3 id="11-마이크로태스크-큐와-태스크-큐-차이">11. 마이크로태스크 큐와 태스크 큐 차이</h3>
<ul>
<li>JavaScript의 이벤트 루프는 비동기 코드를 처리할 때 작업을 두가지 큐로 분류해 관리합니다. 태스트 큐와 마이크로태스크 큐입니다. 태스크 큐는 setTimeout, setInterval, setImmediate, DOM 이벤트 등이 여기에 들어갑니다. 즉, 브라우저 API에 의해 예약된 일반적인 비동기 작업이 저장되는 공간입니다. 마이크로태스크 큐는 Promise.then, catch, finally, queueMicrotask, MutationObserver 등이 여기에 들어갑니다. 태스크 큐보다 우선적으로 실행되는 짧은 비동기 작업들을 처리합니다. 이벤트 루프는 태스트 큐를 하나 실행한 뒤, 그 즉시 마이크로태스트 큐에 쌓인 모든 작업을 먼저 비워냅니다 이후 다시 태스트 큐로 넘어가는 순서로 반복됩니다.</li>
</ul>
<h3 id="12-javascript의-이벤트-루프와-비동기-처리-방식에-대해-설명해주세요-콜백-promise-asyncawait-차이도-함께요">12. JavaScript의 이벤트 루프와 비동기 처리 방식에 대해 설명해주세요. 콜백, Promise, async/await 차이도 함께요.</h3>
<ul>
<li>JavaScript는 단일 스레드 언어이기 때문에 동시에 여러 작업을 직접 처리할 수 없습니다. 하지만 이벤트 루프를 통해 비동기 작업을 효율적으로 처리합니다. 이벤트 루프는 콜 스택이 비었을 때 태스크 큐의 콜백 함수들을 하나씩 꺼내 실행하며 비동기 코드를 순차적으로 관리합니다. 비동기 처리를 구현하는 방식에는 3가지가 있습니다. 콜백은 함수가 끝난 뒤 실행될 함수를 인자로 전달하는 방식이지만, 중첩 구조로 인해 콜백 지옥이 생길 수 있습니다. 이를 개선하기 위해 Promise가 도입되었고 then() 체이닝을 통해 비동기 흐름을 구조적으로 관리할 수 있게 되었습니다. 마지막으로 async/await은 Promise를 더 직관적으로 사용할 수 있는 문법으로, 코드를 동기식처럼 읽히게 하면서도 비동기 동작을 유지할 수 있습니다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 면접 질문 모음 (1)]]></title>
            <link>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-1</link>
            <guid>https://velog.io/@gayeong__0916/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-1</guid>
            <pubDate>Wed, 29 Oct 2025 06:13:02 GMT</pubDate>
            <description><![CDATA[<h1 id="html--css">HTML &amp; CSS</h1>
<h3 id="1-semantic-tag의-개념과-사용하면-좋은-점">1. Semantic Tag의 개념과 사용하면 좋은 점</h3>
<ul>
<li>HTML에서 태그 이름만 봐도 그 안에 담긴 내용의 의미나 역할을 알 수 있도록 만든 태그
Semantic Tag를 사용하면 코드의 의미를 명확하게 표현할 수 있습니다. 단순히 div로만 구성된 구조보다, header나 main, article같은 태그를 사용하면 브라우저나 개발자, 보조기기 모두가 해당 영역의 역할을 명확히 이해할 수 있습니다. 이를 통해 스크린 리더가 영역의 역할을 정확히 파악할 수 있어서 접근성이 높아지고 코드의 가독성과 유지보수성도 향상됩니다. 또한 검색 엔진이 페이지 구조를 더 잘 인식하기 때문에 SEO 측면에서도 유리합니다. 즉 Sematic Tag는 단순히 시각적 구분이 아니라 의미있는 구조와 접근성, 검색 효율성까지 개선하는 요소라고 생각합니다. </li>
</ul>
<h3 id="2-메타-태그의-용도는-무엇인가요">2. 메타 태그의 용도는 무엇인가요?</h3>
<ul>
<li><code>&lt;meta&gt;</code> 태그는 문서의 메타데이터를 정의하는데 사용하고 문서의 정보를 브라우저나 검색 엔진, 외부 서비스에 전달하는 역할을 합니다.</li>
</ul>
<h3 id="3-이미지-태그에서-alt-속성의-중요성은-무엇인가요">3. 이미지 태그에서 alt 속성의 중요성은 무엇인가요?</h3>
<ul>
<li><code>alt</code> 속성은 이미지가 로드되지 않았을 때 표시되는 대체 텍스트로, 시각 장애인 사용자가 화면 읽기 프로그램을 통해 이미지를 인식할 수 있도록 돕습니다. 또한 검색 엔진은 이미지를 직접 해석할 수 없기 때문에 <code>alt</code> 속성을 참고해 이미지의 의미를 이해합니다. 따라서 올바른 <code>alt</code> 텍스트를 작성하면 접근성을 높이는 동시에 SEO에도 긍정적인 영향을 줍니다.</li>
</ul>
<h3 id="4-href와-src의-차이점에-대해-설명하세요">4. href와 src의 차이점에 대해 설명하세요.</h3>
<ul>
<li><code>href</code>는 하이퍼링크 참조로, 페이지 간 이동이나 외부 문서를 연결하기 위해 URL을 지정할 때 사용합니다. 예를 들어 <code>&lt;a&gt;</code>나 <code>&lt;link&gt;</code> 태그에서 사용되어 다른 문서나 리소스로 이동할 수 있도록 합니다.
반면 <code>src</code>는 source의 약자로, 이미지나 스크립트, 비디오 등 외부 리소스를 현재 문서에 불러와 삽입할 때 사용합니다. 즉, href는 연결(참조) 의 개념이고, src는 불러오기(삽입) 의 개념입니다.</li>
</ul>
<h3 id="5-margin과-padding이란">5. margin과 padding이란?</h3>
<ul>
<li>CSS에서 margin과 padding 모두 요소의 여백을 지정할 때 사용하지만, 적용되는 위치가 다릅니다. margin은 요소의 바깥쪽 여백으로, 요소와 요소 사이의 간격을 조절할 때 사용합니다. 반면 padding은 요소의 안쪽 여백으로, 요소의 콘텐츠와 테두리 사이의 간격을 조정할 때 사용되며 width에 포함되기 때문에 박스 크기에 영향을 줍니다. 즉 margin은 요소 간의 간격을, padding은 요소 내부의 여유 공간을 조절한다고 정리할 수 있습니다. </li>
</ul>
<h3 id="6-postion이란">6. postion이란?</h3>
<ul>
<li>CSS에서 position은 요소의 위치를 어떻게 배치할지를 지정하는 속성입니다. 즉, 요소가 문서의 흐름에서 어떤 기준으로 배치될지를 결정합니다. postion에는 대표적으로 static, relative, absolute, fixed, sticky 다섯가지가 있습니다. static은 기본값으로 요소가 문서의 일반적인 흐름에 따라 배치됩니다. relative는 요소를 원래 위치를 기준으로 이동시킬 수 있습니다. absolute는 일반 흐름에서 벗어나며, 가장 가까운 position이 지정된 부모 요소를 기준으로 배치됩니다. fixed는 뷰포트를 기준으로 고정되어, 스크롤해도 위치가 변하지 않습니다. sticky는 스크롤 위치에 따라 relative와 fixed가 번갈아 적용되어, 특정 지점에 도달하면 화면에 고정되는 형태로 작동합니다. </li>
</ul>
<h3 id="7-css에서-sassscss의-장점">7. CSS에서 sass(scss)의 장점</h3>
<ul>
<li>Sass는 css를 더 효율적으로 작성할 수 있게 도와주는 CSS 전처리기입니다. 일반 CSS보다 구조적이고 재사용성이 높은 코드를 작성할 수 있다는 장점이 있습니다. 대표적인 특징으로는 첫번째, 변수를 사용해 색상, 폰트, 크기 등을 재사용할 수 있고 두번째, 중첩으로 선택자 구조를 직관적으로 표현할 수 있습니다. 세번째, 믹스인과 함수를 활용하면 반복되는 스타일을 재사용할 수 있습니다. 마지막으로, 상속으로 공통 속성을 여러 요소에 쉽게 적용할 수 있습니다. </li>
</ul>
<h3 id="8-cascading">8. Cascading</h3>
<ul>
<li>CSS의 Cascading은 여러 스타일이 한 요소에 적용될 때, 우선순위에 따라 단계적으로 적용되는 방식을 의미합니다. 3가지 기준으로 어떤 스타일이 최총적으로 적용될지를 결정합니다. 먼저, 명시도 선택자의 구체적인 정도이며 두번째, 소스 순서 같은 우선순위일경우, 나중에 선언된 스타일이 적용됩니다. 마지막으로 중요도, <code>!important</code>가 붙은 스타일은 최우선으로 적용이 됩니다. 이런 과정을 통해 여러 스타일이 충돌하더라도 일관된 규칙에 따라 최종 스타일이 결정되는것이 Cascading입니다. </li>
</ul>
<h3 id="9-flexbox와-grid의-차이">9. Flexbox와 Grid의 차이</h3>
<ul>
<li>Flexbox와 Grid는 모두 CSS의 레이아웃을 구성하기 위한 방법이지만, 가장 큰 차이점은 배치의 차원에 있습니다. Flexbox는 1차원 레이아웃으로, 요소를 가로 또는 세로 한 방향으로 정렬할 때 사용합니다. 주로 버튼 그룹, 네비게이션 바처럼 일렬 정렬이 필요한 경우에 적합합니다. 반면 Grid는 2차원 레이아웃으로, 행과 열 모두를 동시에 제어할 수 있습니다. 페이지 전체 레이아웃처럼 복잡한 구조를 만들 때 유용합니다. 즉, Flexbox는 한 줄 정렬 중심, Grid는 행과 열이 있는 2D 구조 중심으로 구분할 수 있습니다. </li>
</ul>
<h3 id="10-css의-박스-모델에-대해-설명하세요">10. CSS의 박스 모델에 대해 설명하세요.</h3>
<ul>
<li>CSS의 박스 모델은 모든 HTML 요소를 하나의 사각형 박스로 보고, 이 박스를 구성하는 영역들의 관계를 정의한 개념입니다. 박스 모델은 안쪽부터 content, padding, border, margin의 영역으로 구성됩니다. content는 실제 내용이 표시되는 영역이고, padding은 콘텐츠와 테두리 사이의 여백, border는 요소의 테두리, margin은 요소와 다른 요소 사이의 바깥 여백을 의미합니다. 요소의 전체 크기는 content + padding + borer + margin으로 계산되며, box-sizing:border-box 속성을 사용하면 padding과 border가 width 안에 포함되어 크기 계산이 달라집니다.</li>
</ul>
<h3 id="11-box-sizing을-border-box로-설정하면-어떤-차이가-생길까">11. box-sizing을 border-box로 설정하면 어떤 차이가 생길까?</h3>
<ul>
<li>CSS에서 box-sizing을 border-box로 설정하면, 요소의 width와 height 계산 방식이 달라집니다. 기본값인 content-box에서는 width가 콘텐츠 영역만을 의미하기 때문에 paddig이나 border를 추가하면 실제 요소의 전체 크기가 커집니다. 반면 border-box를 사용하면 padding과 border가 width와 height에 포함되어 계산됩니다. 즉 요소의 총 크기가 늘어나지 않고, 지정한 width 안에서 내부 여백과 테두리가 함께 계산됩니다. 이 방식은 레이아웃을 잡을 때 크기 계산을 단순하게 만들어 예상치 못한 오버플로우나 깨짐을 방지할 수 있습니다. </li>
</ul>
<h3 id="12-display-block과-display-inline의-차이점은-무엇인가요">12. display: block과 display: inline의 차이점은 무엇인가요?</h3>
<ul>
<li>block와 inline은 요소가 화면에 배치되는 방식을 결정하는 속성입니다. block 요소는 항상 새로운 줄에서 시작하며, 기본적으로 부모 요소의 가로 너비를 전부 차지합니다. width, height, margin, padding 속서을 자유롭게 적용할 수 있습니다. 대표적인 예로는 <code>&lt;div&gt;, &lt;p&gt;, &lt;section&gt;</code>등이 있습니다. 반면 inline 요소는 같은 줄 안에 배치되며, 콘텐츠의 크기만큼만 공간을 차지합니다. weight와 height 속성은 적용되지 않고 margin과 padding은 좌우 방향만 부분적으로 적용됩니다. 대표적인 예로는 <code>&lt;span&gt;, &lt;a&gt;, &lt;strong&gt;</code> 등이 있습니다. 즉, block은 한 줄 전체를 차지하는 박스, inline은 내용만큼 차지하는 인라인 요소로 구분할 수 있습니다. </li>
</ul>
<h3 id="13-css와-html5를-비교설명해-보세요">13. CSS와 HTML5를 비교설명해 보세요</h3>
<ul>
<li>HTML5와 CSS는 모두 웹페이지를 구성하는 핵심 기술이지만, 역할이 다릅니다. HTML5는 웹 페이지의 구조와 내용을 정의하는 언어입니다. 반면 CSS는 HTML로 구성된 구조에 디자인과 레이아웃을 적용하는 스타일 언어입니다. 즉 HTML5는 웹의 뼈대(구조)를 만들고, CSS는 그 위에 살(디자인)을 입히는 역할을 한다고 볼 수 있습니다. </li>
</ul>
]]></description>
        </item>
    </channel>
</rss>