LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

ReactuseEffectcleanup의존성 배열useRefstale closure트러블슈팅

useEffect cleanup과 의존성 배열 — 실전 버그 사례로 이해하는 생애주기

2026년 4월 1일·13분 읽기

useEffect의 생애주기부터 정리하자

버그를 이해하려면 먼저 useEffect의 실행 흐름을 정확히 알아야 한다. useEffect는 세 가지 시점에 동작한다.

  1. 마운트 — 컴포넌트가 DOM에 처음 삽입될 때 effect 함수가 실행된다.
  2. 의존성 변경 — 의존성 배열에 있는 값이 바뀌면 이전 effect의 cleanup을 먼저 실행하고, 새 effect 함수를 실행한다.
  3. 언마운트 — 컴포넌트가 DOM에서 제거될 때 마지막 cleanup이 실행된다.

여기서 핵심은 2번이다. 의존성이 변경되면 cleanup과 재실행이 한 쌍으로 발생한다. 이 사이클이 예상치 못한 시점에 끼어들면 외부 상태가 꼬인다.

마운트     → effect 실행 (prev 캡처 → overflow = "hidden")
의존성 변경 → cleanup (overflow = prev) → effect 재실행 (prev 다시 캡처 → overflow = "hidden")
언마운트   → cleanup (overflow = prev)

cleanup이 복원하는 값은 해당 effect가 실행된 시점에 캡처한 값 이다. effect가 재실행될 때마다 새로운 클로저가 생기고, 그 클로저 안의 prev도 새로 캡처된다. 이 점을 놓치면 아래와 같은 버그가 생긴다.


버그: 모바일에서 스크롤이 멈췄다

블로그의 프레젠테이션 모드에서 모바일 스크롤이 완전히 멈추는 버그가 발생했다. 프레젠테이션을 닫아도 스크롤이 돌아오지 않았다. document.body.style.overflow가 "hidden"으로 고착되어 있었다.

문제가 된 코드

tsx
// PresentationView 내부
useEffect(() => {

Post Q&A

오케이징에게 물어보기

useEffect cleanup과 의존성 배열 — 실전 버그 사례로 이해하는 생애주기 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/SEOJing
파일 15개, 폴더 1개
Cloudflare Workers에서 fs 모듈이 안 되는 이유와 해결법대표 이미지 자동화 실험 — 검색과 Codex 생성이 같은 경로로 붙었다본문 이미지를 나중에 넣는 게 아니라 — SEOJing 글쓰기 파이프라인에 시각 판단을 넣기모바일 웹에서 가로 모드를 강제하는 5가지 방법 — iOS Safari에서도 동작하는 코드 뷰어 만들기블로그 글을 PPT로 만들기 — DOM 클로닝 기반 프레젠테이션 모드100vh가 100%가 아닌 이유 — 모바일 뷰포트 단위 완전 정리Context로 퀴즈 컴포넌트를 만들다 막혀서 React.Children을 공부하게 된 이야기대표 이미지를 글마다 다시 붙이는 방식 — 사진 검색에서 리소그래프 배경까지글 위에 영상을 붙인다는 것 — SEOJing 요약 쇼츠 파이프라인localStorage 읽기에서 하이드레이션 에러가 터지는 이유 useSyncExternalStore로 해결useEffect cleanup과 의존성 배열 — 실전 버그 사례로 이해하는 생애주기vinext + GitHub Actions로 Cloudflare Workers 배포하기vinext 오픈소스 기여기: 한국어 slug가 RSC에서 이슈를 일으킨 이유RSC 환경에서 WebAssembly가 차단되는 이유 — Shiki에서 rehype-prism-plus로vinext는 왜 빠를까? — SSR, Vite, Edge, 그리고 Web Vitals까지

같은 섹션의 대표 이미지

39 posts · latest first
본문 이미지를 나중에 넣는 게 아니라 — SEOJing 글쓰기 파이프라인에 시각 판단을 넣기 글의 대표 이미지
SEO Jing26. 06. 22.

본문 이미지를 나중에 넣는 게 아니라 — SEOJing 글쓰기.

SEOJing에서 새 글을 쓸 때 대표 이미지와 본문 이미지를 빼먹지 않도록, 블로그 맵과 글쓰기 파이프라인 안에 시각 판단 단계를 넣은 과정을 정리했다.

26. 06. 22.SEOJing
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
if (fullscreenCode) {
setFullscreenCode(null);
} else {
safeClose();
}
}
};
document.addEventListener("keydown", handleKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", handleKey);
};
}, [safeClose, totalSlides, fullscreenCode, goToNext]);

이 effect는 프레젠테이션 모드가 열릴 때 스크롤을 잠그고, 닫힐 때 원래 값으로 복원한다. 언뜻 보면 문제가 없다. 하지만 의존성 배열에 fullscreenCode가 들어 있다.

재현 시나리오

프레젠테이션 모드(PresentationView)와 코드 전체보기(FullscreenView)가 중첩된 상황에서 문제가 발생한다. 단계별로 보자.

  1. PresentationView 마운트 — effect 실행. prev = ""(기본값)을 캡처하고 overflow = "hidden" 설정.
  2. 코드 전체보기 클릭 — fullscreenCode 상태가 변경된다. 이 값이 의존성 배열에 있으므로 cleanup 실행 → overflow = ""(prev) → effect 재실행 → prev = ""를 캡처하고 다시 overflow = "hidden".
  3. FullscreenView 마운트 — 이 컴포넌트도 자체 effect에서 overflow = "hidden"을 설정한다. 이때 prev = "hidden"을 캡처한다.
  4. FullscreenView 닫힘 — cleanup이 overflow = prev를 실행한다. prev는 "hidden"이므로 overflow = "hidden" 그대로.
  5. 코드 전체보기 닫기 — fullscreenCode가 null로 변경. PresentationView의 cleanup 실행 → overflow = ""로 복원. effect 재실행 → 이 시점에서 prev = "hidden"을 캡처한다. FullscreenView의 cleanup이 "hidden"을 남겼기 때문이다.
  6. 프레젠테이션 종료 — 언마운트 cleanup 실행. overflow = prev → . 원래 값()이 아닌 으로 "복원"된다.

결과적으로 프레젠테이션을 닫아도 overflow: hidden이 남아서 페이지 스크롤이 불가능해진다. 모바일에서는 터치 스크롤이 아예 먹통이 되므로 사용자가 페이지를 떠나는 수밖에 없다.


원인: 의존성 배열이 effect를 필요 이상으로 재실행시켰다

근본 원인은 fullscreenCode가 의존성 배열에 포함되어 있었다 는 것이다. 이 값이 바뀔 때마다 effect 전체가 cleanup → 재실행 사이클을 탔다.

effect가 재실행될 때마다 const prev = document.body.style.overflow를 새로 캡처한다. 그런데 다른 컴포넌트(FullscreenView)가 중간에 overflow를 건드리면, 재실행 시점의 prev가 원래 값이 아닌 "hidden"이 된다. 이후 cleanup이 이 오염된 값으로 "복원"하면서 상태가 꼬인다.

fullscreenCode는 왜 의존성 배열에 있었을까? handleKey 함수 안에서 fullscreenCode를 참조하고 있었기 때문이다. ESLint의 react-hooks/exhaustive-deps 규칙이 이 값을 의존성에 넣으라고 경고했을 가능성이 높다. 린트 규칙을 맹목적으로 따른 결과 effect의 의미가 변질된 것이다.

이 effect가 정말로 재실행되어야 하는 시점은 프레젠테이션 모드의 마운트/언마운트뿐이다. fullscreenCode가 바뀌었다고 스크롤 잠금을 해제했다가 다시 거는 것은 의미 없는 동작이다. "이 값이 변할 때 effect를 재실행해야 하는 이유가 있는가?"라는 질문에 답할 수 없다면, 의존성 배열에서 제거해야 한다.


해결: ref로 최신값을 참조한다

의존성 배열에서 fullscreenCode를 제거하면 되지만, handleKey 안에서 이 값을 참조해야 한다. 의존성에서 빼면 stale closure가 되어 항상 초기값만 읽는다. 이 문제를 해결하는 표준 패턴이 ref를 통한 최신값 참조다.

tsx
const fullscreenCodeRef = useRef(null);
fullscreenCodeRef.current = fullscreenCode; // 렌더링마다 최신값 동기화

useEffect(() => {
  const prev = document.body.style.overflow;
  document.body.style.overflow = "hidden";

  const handleKey = (e: KeyboardEvent) => {
    if (e.key === "Escape") {
      if (fullscreenCodeRef. 

fullscreenCodeRef.current는 항상 최신 상태값을 가리킨다. ref는 값이 바뀌어도 React의 렌더링을 트리거하지 않고, 의존성 배열에 넣을 필요도 없다. effect 안의 이벤트 핸들러가 ref를 통해 값을 읽으므로 stale closure 문제도 없다.

이제 fullscreenCode가 변해도 effect는 재실행되지 않는다. cleanup → 재실행 사이클이 발생하지 않으므로 prev가 오염될 일이 없다. 프레젠테이션 모드를 닫으면 마운트 시점에 캡처한 원래 overflow 값으로 정확하게 복원된다.


cleanup이 복원하는 대상이 외부 상태일 때 주의할 점

이 버그에서 배울 수 있는 더 넓은 교훈이 있다. cleanup에서 DOM이나 전역 객체 같은 외부 상태를 복원할 때는 각별히 주의해야 한다.

React의 상태(useState, useReducer)는 React가 관리한다. 값이 어떤 시점에 어떻게 변하는지 React가 추적한다. 하지만 document.body.style은 React 바깥에 있다. 어떤 컴포넌트든, 어떤 라이브러리든 이 값을 바꿀 수 있다. effect가 캡처한 prev는 캡처 시점의 스냅샷일 뿐, 이후 다른 코드가 이 값을 바꿨는지 알 수 없다.

따라서 외부 상태를 조작하는 effect에서는 두 가지를 확인해야 한다.

  1. effect가 최소한으로 재실행되는가 — 의존성 배열에 불필요한 값이 없는지 확인한다. 재실행 횟수가 늘어날수록 캡처 시점의 외부 상태가 오염될 가능성이 높아진다.
  2. 복원 값이 정말 원래 값인가 — 여러 컴포넌트가 같은 외부 상태를 건드린다면, cleanup 시점에 캡처한 값이 진짜 "원래" 값이 아닐 수 있다. 참조 카운팅이나 스택 기반 관리가 더 안전할 수 있다.

stale closure 해결 패턴 정리

ref로 최신값을 참조하는 패턴은 useEffect와 함께 자주 쓰인다. 어떤 상황에서 이 패턴이 필요한지 정리해보자.

패턴이 필요한 경우

effect 안에서 어떤 상태값을 읽기만 하고, 그 값이 변할 때 effect를 재실행할 필요가 없을 때 사용한다. 위 사례에서 fullscreenCode는 이벤트 핸들러 안에서 조건 분기에만 쓰인다. 이 값이 바뀐다고 이벤트 리스너를 해제했다가 다시 등록할 이유가 없다.

tsx
// 패턴
const valueRef = useRef(value);
valueRef.current = value; // 매 렌더링마다 동기화

useEffect(() => {
  const handler = () => {
    // valueRef.current로 항상 최신값 읽기
    console.log(valueRef.current);
  };
  window.addEventListener("some-event", handler);
  return () => window.removeEventListener("some-event", handler);

패턴을 쓰면 안 되는 경우

값이 변할 때 effect가 실제로 다른 동작을 해야 하는 경우에는 이 패턴을 쓰면 안 된다. 예를 들어 API 엔드포인트 URL이 바뀌면 이전 요청을 취소하고 새 요청을 보내야 한다. 이때는 URL이 의존성 배열에 있어야 맞다.

tsx
// 이 경우는 ref가 아니라 의존성 배열이 맞다
useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal }).then(/* ... */);
  return () => controller.abort(); // 이전 요청 취소
}, [url]); // url이 바뀌면 재실행해야 한다

판단 기준은 간단하다 — "이 값이 변할 때 cleanup → 재실행이 의미 있는 동작인가?" 답이 "아니오"라면 ref 패턴을 사용한다.


핵심 정리

useEffect의 의존성 배열은 "이 값이 바뀌면 effect를 재실행하라"는 선언이다. 여기에 값을 추가하는 것은 단순히 린트 경고를 없애는 행위가 아니라, cleanup → 재실행 사이클의 빈도를 결정하는 설계 행위다.

  1. 의존성 배열의 모든 값에 질문하라 — "이 값이 변할 때 effect가 재실행되어야 하는 이유가 있는가?"
  2. 읽기만 하는 값은 ref로 빼라 — effect를 재실행하지 않으면서 최신값을 읽을 수 있다.
  3. 외부 상태를 조작하는 effect는 재실행을 최소화하라 — cleanup이 복원하는 값이 오염될 수 있다.
  4. 여러 컴포넌트가 같은 외부 상태를 건드리면 경쟁 조건이 생긴다 — 단순한 prev/restore 패턴으로는 부족할 수 있다.

ESLint의 react-hooks/exhaustive-deps는 유용한 도구이지만, 모든 의존성을 맹목적으로 추가하라는 의미가 아니다. effect의 의도를 이해하고, 의존성 배열을 의식적으로 설계해야 한다.

SEO Jing26. 06. 22.

글 위에 영상을 붙인다는 것 — SEOJing 요약.

SEOJing 글을 소셜용 영상으로 따로 소비시키는 게 아니라, 포스트 상단 요약과 블로그 유입 장치로 연결하기 위해 summaryVideo frontmatter와 Supertonic3 기반 요약 쇼츠 파이프라인을 붙인 과정을 정리했다.

26. 06. 22.SEOJing
대표 이미지 자동화 실험 — 검색과 Codex 생성이 같은 경로로 붙었다 글의 리소그래프 스타일 대표 이미지 배경
SEO Jing26. 06. 21.

대표 이미지 자동화 실험 — 검색과 Codex 생성이 같은.

SEOJing 블로그에 대표 이미지를 자동으로 붙이는 실험을 실제로 돌려봤다. 검색 기반 cover 삽입과 Codex CLI 기반 정적 SVG 생성이 같은 frontmatter 경로로 연결됐다.

26. 06. 21.SEOJing
대표 이미지를 글마다 다시 붙이는 방식 글의 리소그래프 스타일 대표 이미지 배경
SEO Jing26. 06. 21.

대표 이미지를 글마다 다시 붙이는 방식 — 사진 검색에서.

SEOJing 포스트 목록을 파일 탐색기처럼만 두지 않고, 최신 글부터 실제 사진 기반 리소그래프 배경을 붙이는 실험을 정리합니다. 이미지는 배경만 만들고, 제목과 아이콘은 블로그 UI가 맡는 쪽으로 방향을 바꿨습니다.

26. 06. 21.SEOJing
SEO Jing26. 04. 01.

Day 12 - 테스트 커버리지 개선, 모바일 프레젠테이션 버그 2건.

SEO Jing 개발 열두째 날. code-block 테스트 14개 추가로 커버리지 대폭 개선, 모바일 프레젠테이션에서 FullscreenView 방향 전환 문제와 스크롤 멈춤 버그 수정.

26. 04. 01.SEOJing
SEO Jing26. 04. 01.

useEffect cleanup과 의존성 배열 — 실전 버그.

useEffect 의존성 배열에 불필요한 값이 포함되면 cleanup과 재실행이 뒤엉켜 DOM 상태가 꼬일 수 있다. 프레젠테이션 모드에서 발생한 모바일 스크롤 고착 버그를 통해 원인과 해결 패턴을 정리한다.

26. 04. 01.SEOJing
SEO Jing26. 03. 25.

Day 11 - 프레젠테이션 확대 기능,.

SEO Jing 개발 열한째 날. PC 프레젠테이션 확대/축소 컨트롤 추가, 모바일 orientation 판단 로직 개선, FullscreenView를 독립 컴포넌트로 분리 및 PC 대응.

26. 03. 25.SEOJing
SEO Jing26. 03. 25.

vinext 오픈소스 기여기: 한국어 slug가 RSC에서.

한국어 MDX 블로그를 만들다 vinext 프레임워크의 ByteString 버그를 발견하고, 이슈를 작성하고, PR을 올리기까지의 과정

26. 03. 25.SEOJing
SEO Jing26. 03. 25.

vinext는 왜 빠를까? — SSR, Vite, Edge,.

vinext가 빠른 이유를 이해하기 위해, SSR부터 Hydration, 빌드 도구, Edge Runtime, Web Vitals, RSC, CDN 캐싱, ISR, PPR까지 웹 렌더링 성능의 전체 그림을 정리한다

26. 03. 25.SEOJing
SEO Jing26. 03. 24.

100vh가 100%가 아닌 이유 — 모바일 뷰포트 단위 완전 정리.

모바일 Safari에서 100vh가 화면을 넘치는 이유, vh/svh/lvh/dvh의 차이, JavaScript에서 실제 뷰포트를 구하는 방법, 그리고 전체화면 UI를 만들 때 알아야 할 CSS zoom과 모바일 판정 패턴까지 정리한다.

26. 03. 24.SEOJing
SEO Jing26. 03. 23.

Day 10 - 프레젠테이션 모드 안정화.

SEO Jing 개발 열째 날. 프레젠테이션 모드의 모바일 UX 문제들을 전면 수정. 퀴즈·코드블록·이미지·포스트목록 처리 개선, 롱프레스 UX 및 하단 바 레이아웃 안정화. 모바일 뷰포트·리스트 분할 문제 수정, 채움 비율 보수적으로 조정, 포스트 탐색기 자연 정렬 적용.

26. 03. 23.SEOJing
SEO Jing26. 03. 22.

Day 9 - 프레젠테이션 모드, 코드블럭 개선, 테스팅.

SEO Jing 개발 아홉째 날. 프레젠테이션 기능 추가, 퀴즈 구조 변경, 모바일 반응형, 코드블럭 사용성, 테스팅 도입.

26. 03. 22.SEOJing
SEO Jing26. 03. 22.

모바일 웹에서 가로 모드를 강제하는 5가지 방법 — iOS.

모바일 웹에서 코드 블록을 가로로 넓게 보여주고 싶었다. screen.orientation.lock()은 iOS에서 안 되고, PWA manifest는 브라우저에서 무시된다. 결국 CSS transform으로 가짜 회전을 만들었고, 그 과정에서 엄지 접근성까지 고민하게 됐다.

26. 03. 22.SEOJing
SEO Jing26. 03. 22.

블로그 글을 PPT로 만들기 — DOM 클로닝 기반.

MDX 파일을 수정하지 않고, 렌더된 DOM을 h2 기준으로 자르고 화면 높이에 맞춰 자동 페이지네이션하는 프레젠테이션 모드를 만들었다. 리스트 높이 측정이 왜 틀리는지 디버깅한 과정과, ul/ol을 li 단위로 분할하는 해결책을 정리한다.

26. 03. 22.SEOJing
SEO Jing26. 03. 21.

Day 8 - 아티클 퀴즈와 스터디 자료.

SEO Jing 개발 여덟째 날. 아티클 퀴즈 디자인시스템 구현과 스터디 대면 자료 작성.

26. 03. 21.SEOJing
SEO Jing26. 03. 21.

Context로 퀴즈 컴포넌트를 만들다 막혀서.

MDX 블로그에 퀴즈 컴포넌트를 만들면서, Context 기반 Compound Component로 시작했다가 index 문제에 막혀 React.Children API를 채택하게 된 과정을 정리한다.

26. 03. 21.SEOJing
SEO Jing26. 03. 20.

Day 7 - Front Matter CMS와 관련 게시물.

SEO Jing 개발 일곱째 날. Front Matter CMS 설치와 관련 게시물 이동 탐색기 구현.

26. 03. 20.SEOJing
SEO Jing26. 03. 18.

Day 6 - 스터디 자료 작성과 데스크탑 비율 수정.

SEO Jing 개발 여섯째 날. 데스크탑 비율 수정과 씨랩 스터디 사전 진단 자료 작성.

26. 03. 18.SEOJing
SEO Jing26. 03. 17.

Day 5 - shiki 제거, MDX 모듈화, 그리고.

SEO Jing 개발 다섯째 날. shiki를 rehype-prism-plus로 교체하고, gray-matter 직접 구현, MDX 모듈화, 페이지 내 검색, 테이블 디자인시스템까지.

26. 03. 17.SEOJing
SEO Jing26. 03. 16.

Day 4 - 배포와 CI/CD.

SEO Jing 개발 넷째 날. lint, codecov, Cloudflare 배포, fs 런타임 이슈.

26. 03. 16.SEOJing
SEO Jing26. 03. 16.

Cloudflare Workers에서 fs 모듈이 안 되는 이유와.

배포 후 블로그 포스트가 404를 반환하던 문제부터, gray-matter eval 차단, next-mdx-remote eval 차단까지 — 세 겹으로 터진 이슈를 하나씩 해결한 기록

26. 03. 16.SEOJing
SEO Jing26. 03. 16.

localStorage 읽기에서 하이드레이션 에러가 터지는 이유.

localStorage를 읽는 컴포넌트에서 하이드레이션 불일치가 발생하는 원인과, useState+useEffect가 아닌 useSyncExternalStore가 정답인 이유를 정리한다.

26. 03. 16.SEOJing
SEO Jing26. 03. 16.

vinext + GitHub Actions로.

vinext 프로젝트를 GitHub Actions로 Cloudflare Workers에 자동 배포하는 방법과 실제 겪은 트러블슈팅 기록

26. 03. 16.SEOJing
SEO Jing26. 03. 16.

RSC 환경에서 WebAssembly가 차단되는 이유 —.

코드 하이라이팅에 Shiki를 쓰면 왜 RSC에서 WebAssembly.instantiate() 에러가 터지는지, 그리고 빌드 타임 하이라이팅으로 어떻게 해결했는지 정리한다.

26. 03. 16.SEOJing
SEO Jing26. 03. 15.

엄청난 피드백.

CLI 코드 리뷰에서 받은 피드백과 전체 코드 수정 계획을 정리했다.

26. 03. 15.SEOJing
SEO Jing26. 03. 15.

생각보다 어려웠던 댓글, 완독 로컬스토리지.

localStorage만으로 글 읽기 추적, 스크롤 진행률, 댓글 감지를 구현한 과정을 정리했다.

26. 03. 15.SEOJing
SEO Jing26. 03. 15.

MDX 관련 이슈 노트.

블로그 디테일 페이지에서 MDX를 렌더링하기 위해 검토한 라이브러리들과 최종 선택 과정.

26. 03. 15.SEOJing
SEO Jing26. 03. 15.

Day 3 - MDX 이슈, 반응형, 다크모드.

SEO Jing 개발 셋째 날. MDX 라이브러리 이슈, 반응형, 코드블럭, 댓글, 다크모드, 코드 리뷰.

26. 03. 15.SEOJing
SEO Jing26. 03. 14.

디자인 시스템을 구축할 때 주의할 점.

디자인 시스템 구현 시 파일 구조, 디자인 토큰, 유의 사항을 정리했다.

26. 03. 14.SEOJing
SEO Jing26. 03. 14.

폰트는 왜 메인 페이지에서만 적용이 안되고 있었을까?.

Tailwind v4 환경에서 폰트가 메인 페이지에서만 적용되지 않던 원인과 Hydration Mismatch 이슈를 정리했다.

26. 03. 14.SEOJing
SEO Jing26. 03. 14.

MDX DOM 트리 파싱하기.

MDX 파일의 경로 탐색 로직과 콘텐츠 트리 생성 과정을 정리했다.

26. 03. 14.SEOJing
SEO Jing26. 03. 14.

결국 Node.js 까지 와버렸다.

MDX 파일 구조를 JSON으로 변환하기 위해 Node.js의 fs 모듈을 배워봤다.

26. 03. 14.SEOJing
SEO Jing26. 03. 14.

Day 2 - 블로그 스켈레톤과 MDX 파싱.

SEO Jing 개발 둘째 날. 디자인 시스템 확장, 블로그 스켈레톤, 폰트 이슈 해결.

26. 03. 14.SEOJing
SEO Jing26. 03. 13.

전체적인 플로우.

SEO Jing 프로젝트의 기술 스택 선정과 전체적인 개발 플로우 정리.

26. 03. 13.SEOJing
SEO Jing26. 03. 13.

Storybook으로 디자인 시스템 테스팅하기.

Storybook의 사용법과 디자인 시스템 개발에서의 장점을 정리했다.

26. 03. 13.SEOJing
SEO Jing26. 03. 13.

MDX가 뭘까?.

MDX의 개념과 블로그에서 활용하는 이유를 정리했다.

26. 03. 13.SEOJing
SEO Jing26. 03. 13.

Day 1 - 디자인 컨셉과 디자인 시스템.

SEO Jing 개발 첫째 날. 디자인 컨셉 설정과 디자인 시스템 구축을 시작했다.

26. 03. 13.SEOJing
SEO Jing26. 03. 13.

기술 블로그를 직접 제작하게 된 이유.

SEO Jing을 개발하게 된 이유입니다.

26. 03. 13.SEOJing
SEO Jing26. 03. 13.

왜 자꾸 프로젝트가 중단되는지.

프로젝트가 중단되는 이유에 대한 자기 회고입니다.

26. 03. 13.SEOJing
"hidden"
""
"hidden"
current
)
{
// ref로 최신값 참조
setFullscreenCode(null);
} else {
safeClose();
}
}
};
document.addEventListener("keydown", handleKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", handleKey);
};
}, [safeClose, totalSlides, goToNext]); // fullscreenCode 제거됨
}, []); // value를 의존성에 넣지 않는다