πŸ“— Docs νŽ˜μ΄μ§€ κ΅¬ν˜„ν•˜κΈ°

κ°€μ—°Β·2024λ…„ 9μ›” 26일
4

μš°ν…Œμ½”

λͺ©λ‘ 보기
6/10
post-thumbnail

μ§€λ‚œλ²ˆμ— μ½”λ”©ν•΄λ“€μ˜€μ— λŒ€ν•œ UTλ₯Ό μ§„ν–‰ν–ˆλ‹€.
μ‚¬μš©μžλ“€μ΄ λ―Έμ…˜ 생성 μ΄λΌλ˜μ§€, νŽ˜μ–΄λ£Έμ˜ κΈ°λŠ₯듀이 μ™œ μžˆλŠ”κ±΄μ§€, μ–΄λ–€ μˆœμ„œλ‘œ μ§„ν–‰ν•΄μ•Όν•˜λŠ”μ§€ 등에 λŒ€ν•œ 정보가 tooltip μ΄λ‚˜ μ„€λͺ… λͺ‡μ€„λ‘œλŠ” μ„€λͺ…λ˜μ§€ μ•Šμ€ λ“― ν–ˆλ‹€.
κ·Έλž˜μ„œ μ½”λ”©ν•΄λ“€μ˜€λ§Œμ˜ μš©μ–΄μ™€ μ‚¬μš©λ²•λ“€μ— λŒ€ν•œ 정보λ₯Ό λ‹΄κ³  μžˆλŠ” Docs νŽ˜μ΄μ§€λ₯Ό λ§Œλ“€κΈ°λ‘œ ν–ˆλ‹€.

⭐️ mdn 이 μš°λ¦¬κ°€ μ›ν•˜λŠ” λ””μžμΈκ³Ό μœ μ‚¬ν•΄μ„œ 많이 μ°Έκ³ ν–ˆλ‹€!

πŸ’­ κ³ λ―Ό -(1) μ–΄λ“œλ―Ό νŽ˜μ΄μ§€ vs ν•˜λ“œμ½”λ”©

μ–΄λ“œλ―Ό νŽ˜μ΄μ§€λ₯Ό λ§Œλ“€μ–΄μ„œ νŽΈμ§‘,μˆ˜μ •,μ‚­μ œκ°€ κ°€λŠ₯ν•˜κ²Œ ν•  μ§€ ν•˜λ“œμ½”λ”©μœΌλ‘œ 직접 넣어쀄지 κ³ λ―Όν–ˆλ‹€.

μ–΄λ“œλ―Ό νŽ˜μ΄μ§€μ˜ μž₯단점

μ–΄λ“œλ―Ό νŽ˜μ΄μ§€κ°€ μžˆλ‹€λ©΄ λ‚΄μš©μ„ μˆ˜μ •ν•΄μ•Ό ν•  λ•Œ 배포와 μ½”λ“œ μˆ˜μ • 없이 κ°„νŽΈν•˜κ²Œ μ—…λ‘œλ“œ μ‚¬μš©ν•  수 μžˆλ‹€.
λ§Œμ•½ μ–΄λ“œλ―Ό νŽ˜μ΄μ§€κ°€ μžˆλ‹€λ©΄ 자유둜운 μŠ€νƒ€μΌ 변경을 μœ„ν•΄ λ§ˆν¬λ‹€μš΄ ν˜•μ‹μ„ μ œκ³΅ν•΄μ£Όμ–΄μ•Ό ν•  것이고, 각 μ„Ήμ…˜λ§ˆλ‹€ id λ₯Ό λ„£μ–΄μ£Όμ–΄μ•Ό ν•œλ‹€. 그리고 μƒˆλ‘œμš΄ μ„œλ²„ api κ°€ ν•„μš”ν•˜λ‹€.
ν•˜μ§€λ§Œ μ½”λ”©ν•΄λ“€μ˜€ κ°€μ΄λ“œλŠ” ν•œλ²ˆ μ œλŒ€λ‘œ μž‘μ„±μ΄ 되면 이후에 변경될 일이 μ—†μ–΄μ„œ 이 λ¬Έμ„œ λ•Œλ¬Έμ— λ°±μ—”λ“œμ—μ„œ μΆ”κ°€ apiλ₯Ό μƒμ„±ν•˜κ³ , ν”„λ‘ νŠΈμ—μ„œλ„ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•΄ μ£ΌλŠ”κ²Œ μ˜€λ²„ μ—”μ§€λ‹ˆμ–΄λ§ 이라고 μƒκ°ν–ˆλ‹€. κ·Έλž˜μ„œ μ§€κΈˆ λ‹Ήμž₯은 ν•˜λ“œμ½”λ”©μœΌλ‘œ κ΅¬ν˜„ν–ˆλ‹€.

πŸ’­ κ³ λ―Ό -(2) 이미지 μ €μž₯을 image 폴더에 vs μ™ΈλΆ€ μ €μž₯μ†Œ

image 폴더에 μ €μž₯ν•˜κ²Œ 된 이유

  • image 폴더에 μ €μž₯ν•˜κ²Œ λœλ‹€λ©΄?
    docs 에 λ“€μ–΄κ°€λŠ” 이미지듀을 λ¦¬μ•‘νŠΈ 내에 μ €μž₯ν•˜κ²Œ λœλ‹€λ©΄ 이미지 μ΅œμ ν™”λ₯Ό ν•˜λ”λΌλ„ λΉŒλ“œ 크기가 컀져 λΉŒλ“œ 속도가 λŠ˜μ–΄λ‚˜κ³  git μ €μž₯μ†Œμ˜ 크기가 컀져 pull λ°›μ„λ•Œμ˜ μ‹œκ°„ λ˜ν•œ λŠ˜μ–΄λ‚  것이닀.

ν•˜μ§€λ§Œ 이 이유 λ•Œλ¬Έμ— 이미지λ₯Ό λ°°ν¬ν•˜κΈ°μ—λŠ” 이것 λ˜ν•œ μ˜€λ²„μ—”μ§€λ‹ˆμ–΄λ§ 이라고 νŒλ‹¨ν–ˆλ‹€.

  • μ™ΈλΆ€ μ €μž₯μ†Œ 이용 κΈ°μ€€
    보톡 μ΄λ―Έμ§€λ“€μ˜ 크기가 5~10M 이상일 λ•Œ μ™ΈλΆ€ μ €μž₯μ†Œ μ΄μš©μ„ κ³ λ €ν•œλ‹€κ³  ν•œλ‹€.

μ•„λž˜ λͺ…λ Ήμ–΄λ₯Ό 톡해 docs ν΄λ”μ˜ 이미지 파일 크기λ₯Ό ν™•μΈν–ˆλ‹€.

du -ch images/docs/*.{jpg,jpeg,png,gif,webp} | grep total | cut -f1


κ·Έ κ²°κ³Ό 4.1M λΌλŠ” κ²°κ³Όκ°€ λ‚˜μ™”λ‹€.
생각보닀 μ’€ λ§ŽκΈ΄ν•œλ°, 5M μ΄ν•˜μ—¬μ„œ λ°°ν¬λŠ” ν•˜μ§€ μ•ŠκΈ°λ‘œ ν–ˆλ‹€.

λŒ€μ‹  μ™ΈλΆ€ μ €μž₯μ†Œλ₯Ό μ‚¬μš©ν•  수 μžˆμ„κ²ƒμ΄λΌλŠ” 것을 κ³ λ €ν•΄μ•Όν•œλ‹€κ³  μƒκ°ν•΄μ„œ

/**
 * 
 * export const DOCS_IMAGES = {
  checkBranchCreated: `${S3_BASE_URL}/check-branch-created.png`,
  μΆ”ν›„ 이미지 배포 μ‹œ λ‹€μŒκ³Ό 같이 url을 λ„£μ–΄μ£Όμ–΄ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 
};
 */
export const DOCS_IMAGES = {
  checkBranchCreated,
  createBranch,
  checkBranchCreatedWebp,
  createBranchWebp,
  createRoomWebp,
  selectMissionWebp,
  startWithMissionWebp,
  ...
};

λ‹€μŒκ³Ό 같이 이미지듀을 ν•œ νŒŒμΌμ— λͺ¨μ•„μ£Όμ—ˆλ‹€. 이 λΆ€λΆ„μ˜ url 만 μˆ˜μ •ν•˜λ©΄ ν•œ 파일 μˆ˜μ •λ§ŒμœΌλ‘œ μ™ΈλΆ€ 이미지 μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

+) μ½”λ”©ν•΄λ“€μ˜€μ˜ ν”„λ‘ νŠΈμ—”λ“œ μ„œλ²„λ₯Ό ec2 -> s3 둜 μ „ν™˜ν•˜μ—¬ 이미지 cdn 캐싱 λ˜ν•œ μ‰½κ²Œ κ°€λŠ₯ν•΄μ‘Œλ‹€. λ”°λΌμ„œ cdn μΊμ‹œλŠ” κ³ λ €ν•˜μ§€ μ•ŠμœΌλ €κ³  ν•œλ‹€!!

κ°„λ‹¨ν•œ μ„±λŠ₯ κ°œμ„ 

  • 이미지 lazy loading
    이미지가 λ§Žμ„λ•Œ 첫 λ‘œλ”© μ‹œ λͺ¨λ“  이미지λ₯Ό λ‘œλ”©ν•˜κ²Œ λœλ‹€λ©΄ μ‹œκ°„μ΄ 였래 κ±Έλ € μ‚¬μš©μž μ΄νƒˆμ΄ 생길 수 μžˆλ‹€. ν•˜μ§€λ§Œ μœ„μ—μ„œ λ§ν–ˆλ“― 이미지 μš©λŸ‰μ΄ 크지 μ•Šμ•„μ„œ intersection Observer 둜 화면에 맞게 lazy λ‘œλ”©μ„ ν•˜μ§€λŠ” μ•Šμ•˜κ³  img νƒœκ·Έμ— 속성 μΆ”κ°€λ‘œ κ°„λ‹¨ν•˜κ²Œλ§Œ μ§€μ •ν•΄μ£Όμ—ˆλ‹€.
  • 이미지 포맷, 이미지 resize
    docs 에 ν•„μš”ν•œ 이미지듀을 λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό 톡해 λͺ¨λ‘ webp 둜 λ°”κΏ”μ£Όμ—ˆκ³ ,
for file in *; do cwebp "$file" -o "${file%.*}.webp"; done 

imagemagick μ΄λΌλŠ” 라이브러리의 도움을 λ°›μ•„ 이미지λ₯Ό 70% μΆ•μ†Œν–ˆλ‹€.

for file in *; do magick $file -resize 70% $file; done

webp λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠλŠ” λΈŒλΌμš°μ €μ—μ„œλŠ” png λ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ img νƒœκ·Έλ„ λ„£μ–΄μ£Όμ—ˆλ‹€.

 <picture>
        <source srcSet={webpSrc} type="image/webp" />
        <img src={src} alt={alt} loading="lazy" />
      </picture>
  • κ°œμ„  κ²°κ³Ό
    <이전>

    <이후>

κ΅¬ν˜„

μ»΄ν¬λ„ŒνŠΈ μš”μ•½ - PR ν™•μΈν•˜κΈ°

μš”κ΅¬μ‚¬ν•­

κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” 뢀뢄에 λŒ€ν•΄ μš”κ΅¬μ‚¬ν•­μ„ μ •λ¦¬ν•΄λ³΄μ•˜λ‹€.

  • μ‚¬μ΄λ“œλ°”μ— μžˆλŠ” λͺ©λ‘μ„ ν΄λ¦­ν•˜λ©΄ ν•΄λ‹Ήν•˜λŠ” μ„Ήμ…˜μœΌλ‘œ 슀크둀이 μ΄λ™ν•΄μ•Όν•œλ‹€.

  • μ™ΈλΆ€μ—μ„œ ν•΄λ‹Ή μ„Ήμ…˜μ˜ 슀크둀 μœ„μΉ˜λ‘œ 이동할 수 μžˆμ–΄μ•Ό ν•œλ‹€.

  • 슀크둀 μ‹œ μ‚¬μ΄λ“œλ°”μ˜ λͺ©λ‘μ—μ„œ ν˜„μž¬ λͺ©λ‘μ΄ ν™œμ„±ν™” λ˜μ–΄μ•Ό ν•œλ‹€.

  • styled component 와 μ»€μŠ€ν…€ ν•œ μ»΄ν¬λ„ŒνŠΈλ₯Ό μ΄μš©ν•΄ μ‰½κ²Œ λ‚΄μš© μΆ”κ°€κ°€ κ°€λŠ₯ν•΄μ•Όν•œλ‹€.
    -> μ»€μŠ€ν…€ ν•œ μ»΄ν¬λ„ŒνŠΈλŠ” μŠ€ν† λ¦¬λΆ 배포λ₯Ό μ°Έκ³ ν•  수 μžˆλ„λ‘ ν–ˆλ‹€.
    μŠ€ν† λ¦¬λΆ

슀크둀 이벀트 κ΅¬ν˜„

(1) 초기 μ‹œμ•ˆ
μ™ΈλΆ€μ—μ„œ ν•΄λ‹Ή 슀크둀 μœ„μΉ˜λ‘œ 이동할 수 있으렀면 url에 ν•΄μ‹œκ°’μ΄ μžˆμ–΄μ•Ό ν•œλ‹€κ³  νŒλ‹¨ν–ˆλ‹€.
κ·Έλž˜μ„œ sidebar λŠ” ν•΄μ‹œκ°’μ„ 가져와 active μƒνƒœλ₯Ό κ²°μ •ν•˜κ³ 
메인docs μ—μ„œλŠ” ν•΄μ‹œκ°’μ— 따라 슀크둀 μœ„μΉ˜κ°€ μ‘°μ •, ν™”λ©΄μ˜ 50% 이상 뢀뢄에 μ„Ήμ…˜μ΄ λ‚˜νƒ€λ‚˜λ©΄ url ν•΄μ‹œκ°’μ΄ λ³€κ²½λ˜λ„λ‘ ν–ˆλ‹€.

μ—¬κΈ°μ„œ λ‹€μŒκ³Ό 같은 λ¬Έμ œκ°€ λ‚˜νƒ€λ‚¬λ‹€.

  • μ‚¬μ΄λ“œλ°” 컨텐츠 클릭 μ‹œ ν•΄λ‹Ή μ»¨ν…μΈ λ‘œ 슀크둀 λœλ‹€.
  • 슀크둀 μ‹œ ν•΄λ‹Ή 컨텐츠가 50% 이상 화면에 λ‚˜μ™”μ„ λ•Œ url ν•΄μ‹œκ°’μ΄ λ³€κ²½λœλ‹€.
    β†’ 이 두 κΈ°λŠ₯이 μΆ©λŒν•΄μ„œ 3의 μœ„μΉ˜μ—μ„œ 1을 ν΄λ¦­ν–ˆμ„λ•Œ 2 컨텐츠가 화면에 λ‚˜μ˜¬λ•Œ url 이 λ³€κ²½λ˜μ–΄ 슀크둀 이동이 λ©ˆμΆ”κ²Œ λœλ‹€.
    β†’ 또, μ•„λž˜λ‘œ 슀크둀 ν•  λ•ŒλŠ” 인식이 λ˜μ§€λ§Œ, μœ„λ‘œ 슀크둀 ν•˜λ©΄ μ μš©λ˜μ§€ μ•ŠλŠ”λ‹€.

이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 고민을 ν–ˆλ‹€.
1. 내뢀에 currentSection μƒνƒœλ₯Ό μΆ”κ°€ν•˜μ—¬ λ”°λ‘œ κ΄€λ¦¬ν•˜κΈ°
2. 슀크둀이 λ‹€ 된 후에 μœ„μΉ˜ μΈμ‹ν•˜λ„λ‘ ν•˜κΈ°

2λ²ˆμ€

if (element) {
        isScrollingRef.current = true;
        element.scrollIntoView({ behavior: 'smooth' });
        setTimeout(() => {
          isScrollingRef.current = false;
        }, 100);
      }

λ₯Ό 톡해 isScrollingRef.current κ°€ false 일 λ•Œλ§Œ ν™”λ©΄ 인식이 될 수 μžˆλ„λ‘ κ΅¬ν˜„ν•  수 μžˆμ—ˆλ‹€. ν•˜μ§€λ§Œ setTimeout 으둜 λΆ€μžμ—°μŠ€λŸ½κ²Œ μˆœμ„œλ₯Ό μ§€μ •ν•΄μ£ΌλŠ”κ²Œ λΆˆν•„μš”ν•œ μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό λ§Œλ“œλŠ” 것이라고 μƒκ°ν•΄μ„œ 1번의 방법을 μ±„νƒν•˜κΈ°λ‘œ ν–ˆλ‹€.

(2) 1단계 κ°œμ„ 
sidebar λŠ” active section 을 props 둜 λ°›μ•„ ν™œμ„±ν™” ν•˜κ³ , λŒ€μ‹  컨텐츠 클릭 μ‹œ ν•΄λ‹Ή λΆ€λΆ„μœΌλ‘œ ν•΄μ‹œκ°’ λ³€κ²½
메인docs μ—μ„œλŠ” ν•΄μ‹œκ°’μ— 따라 슀크둀 μœ„μΉ˜κ°€ μ‘°μ •, 슀크둀 ν•˜λ©΄ active section μƒνƒœκ°€ λ³€κ²½

μ΄λ ‡κ²Œ ν•΄λ‹Ή 버그λ₯Ό ν•΄κ²°ν–ˆλ‹€.

이제 슀크둀 인식이 잘 λ˜μ§€ μ•ŠλŠ” 버그λ₯Ό ν•΄κ²°ν•΄μ•Όν•œλ‹€.

λ‚΄μš©μ΄ λ‹΄κΈ΄ μ„Ήμ…˜μ΄ 화면에 λ‚˜νƒ€λ‚œλ‹€λ©΄ ν™œμ„±ν™” λ˜λ„λ‘ κ΅¬ν˜„μ΄ λ˜μ–΄μžˆλŠ”λ°, μ΄λ ‡κ²Œ ν•˜λ©΄ 1번 μ„Ήμ…˜κ³Ό 2번 μ„Ήμ…˜μ˜ 사이에 μžˆμ„ λ•Œ μ–΄λ–€ μ„Ήμ…˜μ„ ν™œμ„±ν™” ν•  μ§€ λͺ°λΌμ„œ 인식이 λ˜μ§€ μ•ŠλŠ” λ“― ν•˜λ‹€.

(3) 2단계 κ°œμ„ 
κ·Έλž˜μ„œ μ„Ήμ…˜μ˜ titleμ—λ§Œ id λ₯Ό λ„£μ—ˆλ‹€.
λŒ€μ‹  κ°€μž₯ μ™ΈλΆ€μ˜ container에 aria-labelledbyλ₯Ό λ„£μ–΄ ν•œ μ„Ήμ…˜μž„μ„ λͺ…μ‹œν•΄μ£Όμ—ˆλ‹€.

λΉ λ₯΄κ²Œ μŠ€ν¬λ‘€μ„ ν•˜κ±°λ‚˜ μœ„λ‘œ μŠ€ν¬λ‘€μ„ 해도 인식이 잘 됐닀.

μ΅œμ’… μ½”λ“œ scroll

const useHashScroll = () => {
  const location = useLocation();
  const currentHash = location.hash.replace('#', '');
  const [activeSection, setActiveSection] = useState(currentHash);
  const isProcessingRef = useRef(false);

  const handleActiveSection = (section: string) => setActiveSection(section);

  /**
   * 슀크둀 이벀트λ₯Ό κ°μ§€ν•˜μ—¬ ν˜„μž¬ λ·°ν¬νŠΈμ— λ³΄μ΄λŠ” μ„Ήμ…˜μ˜ id 값에 따라 activeSection μ—…λ°μ΄νŠΈ
   * - μš”μ†Œκ°€ λ·°ν¬νŠΈμ— μ§„μž…ν•˜λŠ” μˆœκ°„ ν™œμ„±ν™”
   * - μš”μ†Œκ°€ 뷰포트 μƒλ‹¨μ—μ„œ 보이기 μ‹œμž‘ν•˜λŠ” μˆœκ°„λΆ€ν„° ν•˜λ‹¨μ— λ„λ‹¬ν•˜κΈ° μ§μ „κΉŒμ§€μ˜ λͺ¨λ“  μœ„μΉ˜λ₯Ό 포함
   * - isProcessingRef λ₯Ό 톡해 μ§„ν–‰μ€‘μΌλ•Œ λΆˆν•„μš”ν•œ 계산을 ν•˜μ§€ μ•Šλ„λ‘ 함.
   */
  useEffect(() => {
    const handleScroll = () => {
      if (isProcessingRef.current) return;

      requestAnimationFrame(() => {
        const sections = Array.from(document.querySelectorAll<HTMLElement>('section[id], div[id], p[id]'));
        const viewportHeight = window.innerHeight;

        const visibleSection = sections.find((section) => {
          const rect = section.getBoundingClientRect();
          return rect.top >= 0 && rect.top <= viewportHeight;
        });

        if (visibleSection && visibleSection.id !== activeSection) {
          handleActiveSection(visibleSection.id);
        }

        isProcessingRef.current = false;
      });

      isProcessingRef.current = true;
    };

    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, [activeSection]);

  /**
   * url 의 ν•΄μ‹œκ°’μ„ 기반으둜 슀크둀 이동
   */
  useEffect(() => {
    if (location.hash) {
      const element = document.getElementById(currentHash);
      if (element) {
        element.scrollIntoView({ behavior: 'smooth' });
      }
    }
  }, [location]);

  return { activeSection };
};

export default useHashScroll;

μ•žμœΌλ‘œ κ°œμ„ ν•  일

light house 와 퍼포먼슀 νƒ­μ—μ„œ CPU 6x κ°μ†Œ ν•œ ν›„ μ„±λŠ₯을 μΈ‘μ •ν•΄λ΄€λ‹€.


μΆ”ν›„ κΈ΄ μž‘μ—…μ‹œκ°„μ΄ λ‚˜μ˜¨ 뢀뢄을 뢄석 ν›„ κ°œμ„ ν•΄ 보도둝 ν•  μ˜ˆμ •μ΄λ‹€.

6개의 λŒ“κΈ€

comment-user-thumbnail
2024λ…„ 9μ›” 26일

μ½”λ“œ 볡뢙 μ™„λ£Œ.

1개의 λ‹΅κΈ€
comment-user-thumbnail
2024λ…„ 9μ›” 28일

ν”„λ‘ νŠΈμ—”λ“œ μ„±λŠ₯ κ°œμ„ μ€ 처음 λ΄€λŠ”λ° κ°œμ©Œλ„€μš”..

1개의 λ‹΅κΈ€
comment-user-thumbnail
2024λ…„ 10μ›” 2일

μ§„μ§œ λ©‹μ§€λ„€μš” πŸ‘πŸ‘πŸ‘

1개의 λ‹΅κΈ€