
μ§λλ²μ μ½λ©ν΄λμ€μ λν UTλ₯Ό μ§ννλ€.
μ¬μ©μλ€μ΄ λ―Έμ
μμ± μ΄λΌλμ§, νμ΄λ£Έμ κΈ°λ₯λ€μ΄ μ μλ건μ§, μ΄λ€ μμλ‘ μ§νν΄μΌνλμ§ λ±μ λν μ λ³΄κ° tooltip μ΄λ μ€λͺ
λͺμ€λ‘λ μ€λͺ
λμ§ μμ λ― νλ€.
κ·Έλμ μ½λ©ν΄λμ€λ§μ μ©μ΄μ μ¬μ©λ²λ€μ λν μ 보λ₯Ό λ΄κ³ μλ Docs νμ΄μ§λ₯Ό λ§λ€κΈ°λ‘ νλ€.
βοΈ mdn μ΄ μ°λ¦¬κ° μνλ λμμΈκ³Ό μ μ¬ν΄μ λ§μ΄ μ°Έκ³ νλ€!
μ΄λλ―Ό νμ΄μ§λ₯Ό λ§λ€μ΄μ νΈμ§,μμ ,μμ κ° κ°λ₯νκ² ν μ§ νλμ½λ©μΌλ‘ μ§μ λ£μ΄μ€μ§ κ³ λ―Όνλ€.
μ΄λλ―Ό νμ΄μ§κ° μλ€λ©΄ λ΄μ©μ μμ ν΄μΌ ν λ λ°°ν¬μ μ½λ μμ μμ΄ κ°νΈνκ² μ
λ‘λ μ¬μ©ν μ μλ€.
λ§μ½ μ΄λλ―Ό νμ΄μ§κ° μλ€λ©΄ μμ λ‘μ΄ μ€νμΌ λ³κ²½μ μν΄ λ§ν¬λ€μ΄ νμμ μ 곡ν΄μ£Όμ΄μΌ ν κ²μ΄κ³ , κ° μΉμ
λ§λ€ id λ₯Ό λ£μ΄μ£Όμ΄μΌ νλ€. κ·Έλ¦¬κ³ μλ‘μ΄ μλ² api κ° νμνλ€.
νμ§λ§ μ½λ©ν΄λμ€ κ°μ΄λλ νλ² μ λλ‘ μμ±μ΄ λλ©΄ μ΄νμ λ³κ²½λ μΌμ΄ μμ΄μ μ΄ λ¬Έμ λλ¬Έμ λ°±μλμμ μΆκ° apiλ₯Ό μμ±νκ³ , νλ‘ νΈμμλ μμ‘΄μ±μ μΆκ°ν΄ μ£Όλκ² μ€λ² μμ§λμ΄λ§ μ΄λΌκ³ μκ°νλ€. κ·Έλμ μ§κΈ λΉμ₯μ νλμ½λ©μΌλ‘ ꡬννλ€.
νμ§λ§ μ΄ μ΄μ λλ¬Έμ μ΄λ―Έμ§λ₯Ό λ°°ν¬νκΈ°μλ μ΄κ² λν μ€λ²μμ§λμ΄λ§ μ΄λΌκ³ νλ¨νλ€.
μλ λͺ λ Ήμ΄λ₯Ό ν΅ν΄ 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 μΊμλ κ³ λ €νμ§ μμΌλ €κ³ νλ€!!
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λ₯Ό λ£μ΄ ν μΉμ
μμ λͺ
μν΄μ£Όμλ€.
λΉ λ₯΄κ² μ€ν¬λ‘€μ νκ±°λ μλ‘ μ€ν¬λ‘€μ ν΄λ μΈμμ΄ μ λλ€.
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 κ°μ ν ν μ±λ₯μ μΈ‘μ ν΄λ΄€λ€.


μΆν κΈ΄ μμ
μκ°μ΄ λμ¨ λΆλΆμ λΆμ ν κ°μ ν΄ λ³΄λλ‘ ν μμ μ΄λ€.
μ½λ λ³΅λΆ μλ£.