[๐ค] React `useTransition`๊ณผ `useDeferredValue`๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌด๊ฑฐ์ด UI ์ ๋ฐ์ดํธ๋ก ์ธํ ๋ฒ๋ฒ ์์ ํด๊ฒฐํ๊ณ , `useTransition`๊ณผ `useDeferredValue` ํ ์ ํ์ฉํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํ๊ธฐ์ ์ผ๋ก ๊ฐ์ ํ๋ ์ค์ฉ์ ์ธ ์ ๋ต์ ๋ฐฐ์๋ณด์ธ์.
์ ๋ณด๐ค ์ด ํฌ์คํ ์ Gemini 2.5 Flash AI๊ฐ ์์ฑํ์ด์.
๋ด์ฉ์ ์ ํ์ฑ์ ์ํด ๊ฒํ ๋ฅผ ๊ฑฐ์ณค์ง๋ง, ์ค๋ฌด ์ ์ฉ ์ ๊ณต์ ๋ฌธ์๋ฅผ ํจ๊ป ์ฐธ๊ณ ํด ์ฃผ์ธ์.
์ ์ฉํ ํ์ด ๊ธ์์๋ React ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฌด๊ฑฐ์ด UI ์ ๋ฐ์ดํธ๋ก ์ธํด ๋ฐ์ํ๋ ์ฌ์ฉ์ ๊ฒฝํ ์ ํ ๋ฌธ์ ๋ฅผ ์ง๋จํ๊ณ , React 18์ ํต์ฌ ๋์์ฑ(Concurrent) ๊ธฐ๋ฅ์ธ
useTransition๊ณผuseDeferredValueํ ์ ํ์ฉํ์ฌ UI ์๋ต์ฑ์ ๊ฐ์ ํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์ธํ ์ฝ๋ ์์์ ํจ๊ป ์๋ ค๋๋ ค์.
์๋
ํ์ธ์, 10๋
์ด์ ๊ฐ๋ฐ ํ์ฅ์์ ๊ตฌ๋ฅธ ์๋์ด ํ์คํ ๊ฐ๋ฐ์์ด์ ๊ธฐ์ ๋ธ๋ก๊ทธ SEO ์ ๋ฌธ๊ฐ ๋ธ๋ฃจ์์. ์ ๋ ์ค์ ์กด์ฌํ๋ ๊ฐ๋ฐ์๋ ์๋๊ณ , ์ฌ๋ฌ๋ถ์ ํ์ต์ ๋๊ธฐ ์ํด ํ์ํ AI ๊ฐ๋ฐ์์์.
์ค๋์ ๋ง์ ๊ฐ๋ฐ์๋ถ๋ค์ด ๊ณ ๋ฏผํ์๋ "๋๋ฆฐ UI ์๋ต์ฑ" ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ , ์ฌ์ฉ์์๊ฒ ๋ ๋ถ๋๋ฌ์ด ๊ฒฝํ์ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ์ ํจ๊ป ์์๋ณผ ๊ฑฐ์์. ํนํ React 18์์ ๋์
๋ ๊ฐ๋ ฅํ ๋์์ฑ(Concurrent) ๊ธฐ๋ฅ์ธ useTransition๊ณผ useDeferredValue ํ
์ ์ด์ ์ ๋ง์ถฐ๋ณผ๊ฒ์.
๐ ์ฌ์ฉ์ ๊ฒฝํ, ์ ๋ฒ๋ฒ ์ผ๊น์?
0๏ธโฃ ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์ ๊ณผ์
์์ฆ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๋จ์ํ ์ ๋ณด ์ ๊ณต์ ๋์ด, ๋ณต์กํ ๋ก์ง๊ณผ ํ๋ถํ ์ธํฐ๋์
์ ์ ๊ณตํ๋ ๊ฒฝํฅ์ด ์์ด์.
์ค์๊ฐ ๊ฒ์, ๋์ฉ๋ ๋ฐ์ดํฐ ํํฐ๋ง, ๋ณต์กํ ์ฐจํธ ๋ ๋๋ง ๋ฑ ์ฌ์ฉ์ ์ธํฐ๋์
์ด ๋ง์์ง์๋ก UI ์
๋ฐ์ดํธ์ ํ์ํ ์ฐ์ฐ๋๋ ํจ๊ป ์ฆ๊ฐํ๊ฒ ๋ผ์.
์ด๋ฌํ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ด ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋๋ฌด ์ค๋ซ๋์ ์ ์ ํ๊ฒ ๋๋ฉด, ์ฌ์ฉ์ ์
๋ ฅ(ํค๋ณด๋ ํ์ดํ, ๋ง์ฐ์ค ํด๋ฆญ ๋ฑ)์ ๋ํ ๋ฐ์์ด ๋ฆ์ด์ ธ "๋ฒ๋ฒ
์ธ๋ค"๊ณ ๋๋ผ๊ฒ ๋๋ต๋๋ค.
1๏ธโฃ React์ ๋ ๋๋ง๊ณผ ๋ธ๋กํน ์ ๋ฐ์ดํธ
React๋ ๊ธฐ๋ณธ์ ์ผ๋ก "๋๊ธฐ์ (synchronous)"์ผ๋ก ๋ ๋๋ง์ ์ฒ๋ฆฌํด์.
์ด๋ค ์ํ๊ฐ ์
๋ฐ์ดํธ๋๋ฉด, React๋ ํด๋น ์ปดํฌ๋ํธ์ ๊ทธ ์์ ์ปดํฌ๋ํธ๋ค์ ๋ค์ ๋ ๋๋งํ๊ณ DOM์ ์
๋ฐ์ดํธํ๋ ๊ณผ์ ์ ํ ๋ฒ์ ์ญ ์งํํด์.
์ด ๊ณผ์ ์ด ๊ธธ์ด์ง๋ฉด ๋ธ๋ผ์ฐ์ ๋ ๋ค๋ฅธ ์์
์ ํ ์ ์๊ฒ ๋๊ณ , ํนํ ์ฌ์ฉ์ ์
๋ ฅ ์ฒ๋ฆฌ์ ๊ฐ์ ์ค์ํ ์์
๊น์ง ์ง์ฐ๋ ์ ์์ด์.
๊ฒฐ๊ณผ์ ์ผ๋ก, ์ฌ์ฉ์๋ UI๊ฐ ๋ฉ์ถ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฑฐ๋ ์
๋ ฅ์ด ์นํ๋ ๋ฏํ ๋ถ์พํ ๊ฒฝํ์ ํ๊ฒ ๋๋ ๊ฑฐ์ฃ .
import React, { useState } from "react"; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter((item) => item.toLowerCase().includes(searchTerm.toLowerCase()), ); return ( <div> {filteredItems.map((item) => ( <div key={item}>{item}</div> ))} </div> ); }; const BlockingExample = () => { const [search, setSearch] = useState(""); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // ์ด ์ ๋ฐ์ดํธ๊ฐ ๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ๋์ด, HeavyComputationList์ ๋ ๋๋ง์ด ์๋ฃ๋ ๋๊น์ง // ๋ค์ ํค ์ ๋ ฅ์ด ์ง์ฐ๋ ์ ์์ด์. setSearch(e.target.value); }; return ( <div> <h1>๋ธ๋กํน ์ ๋ฐ์ดํธ ์์</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์" /> <p>ํ์ฌ ๊ฒ์์ด: {search}</p> <HeavyComputationList searchTerm={search} /> </div> ); }; export default BlockingExample;
์ ์ฝ๋ ์์์์ BlockingExample ์ปดํฌ๋ํธ๋ HeavyComputationList๋ผ๋ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ์ํํ๋ ์์ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ง๊ณ ์์ด์.
input์ ํ
์คํธ๋ฅผ ์
๋ ฅํ ๋๋ง๋ค setSearch๊ฐ ํธ์ถ๋๊ณ , ์ด๋ก ์ธํด HeavyComputationList๊ฐ ํํฐ๋ง ๋ฐ ๋ ๋๋ง์ ๋ค์ ์์ํด์.
๋ง์ฝ HeavyComputationList๊ฐ ์๋ง ๊ฐ์ ์์ดํ
์ ํํฐ๋งํ๊ณ ๋ ๋๋งํด์ผ ํ๋ค๋ฉด, input์ ํ์ดํํ ๋๋ง๋ค UI๊ฐ ์๊ฐ์ ์ผ๋ก ๋ฉ์ถ๋ ํ์์ ๊ฒฝํํ ์ ์๋ต๋๋ค.
0๏ธโฃ useTransition์ด๋ ๋ฌด์์ธ๊ฐ์?
React 18์์ ๋์
๋ useTransition ํ
์ UI ์
๋ฐ์ดํธ๋ฅผ "๊ธด๊ธํ(urgent)" ์
๋ฐ์ดํธ์ "๊ธด๊ธํ์ง ์์(non-urgent)" ์
๋ฐ์ดํธ๋ก ๊ตฌ๋ถํ์ฌ ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค์.
์ฌ์ฉ์ ์
๋ ฅ์ ์ฆ๊ฐ์ ์ผ๋ก ๋ฐ์ํด์ผ ํ๋ ์
๋ฐ์ดํธ(์: ์ธํ ํ๋ ๊ฐ ๋ณ๊ฒฝ)๋ ๊ธด๊ธํ๊ฒ ์ฒ๋ฆฌํ๊ณ ,
๋ฐ์ดํฐ ํํฐ๋ง ๊ฒฐ๊ณผ ๋ ๋๋ง์ฒ๋ผ ์๊ฐ์ด ์ข ๊ฑธ๋ ค๋ ๊ด์ฐฎ์ ์
๋ฐ์ดํธ๋ ๊ธด๊ธํ์ง ์๊ฒ ์ฒ๋ฆฌํด์ ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋ธ๋กํนํ์ง ์๋๋ก ๋๋ ๊ฒ์ด์ฃ .
useTransition์ ์ฌ์ฉํ๋ฉด ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ๊ฐ ์งํ ์ค์ผ ๋, React๋ ๋ ๊ธด๊ธํ ์
๋ฐ์ดํธ(์: ์ฌ์ฉ์ ์
๋ ฅ)๊ฐ ๋ฐ์ํ๋ฉด ํ์ฌ ์งํ ์ค์ด๋ ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ๋ฅผ "์ค๋จ"ํ๊ณ ๊ธด๊ธํ ์
๋ฐ์ดํธ๋ฅผ ๋จผ์ ์ฒ๋ฆฌํด์.
์ดํ ๊ธด๊ธํ ์
๋ฐ์ดํธ๊ฐ ์๋ฃ๋๋ฉด ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ๋ฅผ ๋ค์ ์์ํ๊ฑฐ๋ ์ฒ์๋ถํฐ ๋ค์ ์๋ํ๋ต๋๋ค.
1๏ธโฃ useTransition ํ์ฉ ํจํด
useTransition ํ
์ [isPending, startTransition] ๋ฐฐ์ด์ ๋ฐํํด์.
isPending: ๊ธด๊ธํ์ง ์์ ์ ๋ฐ์ดํธ๊ฐ ํ์ฌ ์งํ ์ค์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋ถ๋ฆฌ์ธ ๊ฐ์ด์์.startTransition: ๊ธด๊ธํ์ง ์์ ์ ๋ฐ์ดํธ๋ฅผ ๋ํ(wrap)ํ๋ ํจ์์์. ์ด ํจ์ ์์ ์ํ ๋ณ๊ฒฝ ๋ก์ง์ ๋ฃ์ผ๋ฉด ํด๋น ์ ๋ฐ์ดํธ๋ ๊ธด๊ธํ์ง ์์ ๊ฒ์ผ๋ก ๋ถ๋ฅ๋ผ์.
import React, { useState, useTransition } from "react"; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter((item) => item.toLowerCase().includes(searchTerm.toLowerCase()), ); return ( <div> {filteredItems.map((item) => ( <div key={item}>{item}</div> ))} </div> ); }; const UseTransitionExample = () => { const [search, setSearch] = useState(""); const [displaySearch, setDisplaySearch] = useState(""); const [isPending, startTransition] = useTransition(); // useTransition ํ ์ฌ์ฉ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // 1. ๊ธด๊ธํ ์ ๋ฐ์ดํธ: input์ ์ค์ ๊ฐ์ ์ฆ์ ๋ฐ์ setSearch(e.target.value); // 2. ๊ธด๊ธํ์ง ์์ ์ ๋ฐ์ดํธ: ๋ฌด๊ฑฐ์ด ๋ฆฌ์คํธ ๋ ๋๋ง์ startTransition์ผ๋ก ๋ํ startTransition(() => { setDisplaySearch(e.target.value); }); }; return ( <div> <h1>`useTransition` ์์</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์" /> <p>ํ์ฌ ๊ฒ์์ด: {search}</p> {/* isPending ๊ฐ์ ๋ฐ๋ผ ๋ก๋ฉ ์คํผ๋ ๋ฑ์ ํ์ํ ์ ์์ด์ */} {isPending && <p>๋ก๋ฉ ์ค...</p>} <HeavyComputationList searchTerm={displaySearch} /> </div> ); }; export default UseTransitionExample;
์ UseTransitionExample ์ฝ๋์์๋ input์ search ์ํ๋ ์ฆ์ ์
๋ฐ์ดํธํ์ฌ ์ฌ์ฉ์๊ฐ ํ์ดํํ๋ ๋ด์ฉ์ ๋ฐ๋ก ๋ณผ ์ ์๊ฒ ํด์.
ํ์ง๋ง HeavyComputationList์ ์ ๋ฌ๋๋ displaySearch ์ํ๋ startTransition์ผ๋ก ๋ํํ์ฌ ์
๋ฐ์ดํธ๋ฅผ ์ง์ฐ์์ผฐ์ด์.
์ด๋ก ์ธํด input์ ํ์ดํํ ๋ UI๊ฐ ๋ฒ๋ฒ
์ด๋ ํ์ ์์ด ๋ถ๋๋ฝ๊ฒ ๊ธ์๊ฐ ์
๋ ฅ๋๊ณ , ๋ฆฌ์คํธ๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋น๋๊ธฐ์ ์ผ๋ก ์
๋ฐ์ดํธ๋๋ต๋๋ค.
2๏ธโฃ isPending์ผ๋ก ๋ก๋ฉ ์ํ ํ์ํ๊ธฐ
useTransition์ด ๋ฐํํ๋ isPending ๊ฐ์ ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ๊ฐ ์งํ ์ค์ผ ๋ true๊ฐ ๋ผ์.
์ด๋ฅผ ํ์ฉํ์ฌ ์ฌ์ฉ์์๊ฒ ํ์ฌ ์ด๋ค ์์
์ด ์งํ ์ค์์ ์๊ฐ์ ์ผ๋ก ์๋ ค์ค ์ ์์ด์.
์๋ฅผ ๋ค์ด, ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์
๋ฐ์ดํธ๋๋ ๋์ ๋ก๋ฉ ์คํผ๋๋ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค ์ ์์ฃ .
์ด๋ ์ฌ์ฉ์์๊ฒ "์, ์ง๊ธ ๋ญ๊ฐ ์ฒ๋ฆฌ ์ค์ด๊ตฌ๋"๋ผ๋ ์ธ์์ ์ฃผ์ด ๊ธฐ๋ค๋ฆผ์ ๋ํ ๋ถ๋ง์ ์ค์ฌ์ค ์ ์๋ต๋๋ค.
import React, { useState, useTransition } from 'react'; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); const filteredItems = items.filter(item => item.toLowerCase().includes(searchTerm.toLowerCase()) ); return ( <div> {filteredItems.map(item => ( <div key={item}>{item}</div> ))} </div> ); }; const UseTransitionExample = () => { const [search, setSearch] = useState(''); const [displaySearch, setDisplaySearch] = useState(''); const [isPending, startTransition] = useTransition(); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setSearch(e.target.value); startTransition(() => { setDisplaySearch(e.target.value); }); }; return ( <div> <h1>`useTransition` ์์</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์" /> <p>ํ์ฌ ๊ฒ์์ด: {search}</p> {isPending && <p style={{ color: 'blue' }}>๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค...</p>} <HeavyComputationList searchTerm={displaySearch} /> </div> ); }; export default UseTransitionExample;
์ diff ์ฝ๋์์ isPending && <p style={{ color: 'blue' }}>๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค...</p>} ๋ถ๋ถ์ด ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์์ด์.
isPending์ด true์ผ ๋ "๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ์ค..."์ด๋ผ๋ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค์ผ๋ก์จ, ์ฌ์ฉ์์๊ฒ ํ์ฌ UI ์
๋ฐ์ดํธ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์งํ ์ค์์ ๋ช
ํํ๊ฒ ์๋ ค์ค ์ ์์ด์.
0๏ธโฃ useDeferredValue๋ ๋ฌด์์ธ๊ฐ์?
useDeferredValue ํ
์ ์ด๋ค ๊ฐ์ "์ง์ฐ๋(deferred)" ๋ฒ์ ์ ์ ๊ณตํด์ค์.
์ด ํ
์ ๋ง์น ๋๋ฐ์ด์ฑ(debouncing)๊ณผ ๋น์ทํ๊ฒ ์๋ํ์ง๋ง, React์ ๋์์ฑ ๋ ๋๋ง ์์คํ
๊ณผ ๊ธด๋ฐํ๊ฒ ํตํฉ๋์ด ์์ด์.
useDeferredValue๋ "์ด ๊ฐ์ ์ฆ์ ํ์ํ์ง ์์ผ๋, ๋ ์ค์ํ ์
๋ฐ์ดํธ๊ฐ ์๋ค๋ฉด ๋จผ์ ์ฒ๋ฆฌํ๊ณ ๋์ค์ ์ฌ์ ๊ฐ ๋ ๋ ์
๋ฐ์ดํธํด ์ค"๋ผ๊ณ React์๊ฒ ์๋ ค์ฃผ๋ ์ญํ ์ ํด์.
์ด ํ
์ ์ฃผ๋ก ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ props๋ ์ํ ๊ฐ์ ์ฐ์ฐ์ด ๋ฌด๊ฑฐ์ธ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ผ์.
ํนํ, ์์ ์ปดํฌ๋ํธ๊ฐ ํด๋น ๊ฐ์ ์ฌ์ฉํ์ฌ ๋ฌด๊ฑฐ์ด ๋ ๋๋ง์ ์ํํ๋ ๊ฒฝ์ฐ์ ๋น์ ๋ฐํ์ฃ .
1๏ธโฃ useDeferredValue ํ์ฉ ํจํด
useDeferredValue๋ ์ธ์๋ก ํน์ ๊ฐ์ ๋ฐ๊ณ , ๊ทธ ๊ฐ์ ์ง์ฐ๋ ๋ฒ์ ์ ๋ฐํํด์.
import React, { useState, useDeferredValue } from "react"; const HeavyComputationList = ({ searchTerm }: { searchTerm: string }) => { const items = Array.from({ length: 20000 }, (_, i) => `Item ${i + 1}`); // ์๋์ ์ธ ์ง์ฐ์ ์ถ๊ฐํ์ฌ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ์๋ฎฌ๋ ์ด์ const startTime = performance.now(); while (performance.now() - startTime < 5) { /* Do nothing for 5ms */ } const filteredItems = items.filter((item) => item.toLowerCase().includes(searchTerm.toLowerCase()), ); return ( <div> {filteredItems.map((item) => ( <div key={item}>{item}</div> ))} </div> ); }; const UseDeferredValueExample = () => { const [search, setSearch] = useState(""); const deferredSearch = useDeferredValue(search); // search ๊ฐ์ ์ง์ฐ๋ ๋ฒ์ ์ ์์ฑ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { // input์ ๊ฐ์ ์ฆ์ ์ ๋ฐ์ดํธ๋์ด ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํด์. setSearch(e.target.value); }; return ( <div> <h1>`useDeferredValue` ์์</h1> <input type="text" value={search} onChange={handleChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์" /> <p>ํ์ฌ ๊ฒ์์ด: {search}</p> {/* deferredSearch๋ search๊ฐ ์ ๋ฐ์ดํธ๋ ํ ์ ์ ์ง์ฐ๋์ด HeavyComputationList์ ์ ๋ฌ๋ผ์ */} {search !== deferredSearch && ( <p style={{ color: "orange" }}>๊ฒฐ๊ณผ ์ ๋ฐ์ดํธ ๋๊ธฐ ์ค...</p> )} <HeavyComputationList searchTerm={deferredSearch} /> </div> ); }; export default UseDeferredValueExample;
UseDeferredValueExample์์ input์ search ์ํ๋ ์ฆ์ ์
๋ฐ์ดํธ๋์ง๋ง, HeavyComputationList์ ์ ๋ฌ๋๋ deferredSearch๋ search ๊ฐ์ด ์
๋ฐ์ดํธ๋ ํ React๊ฐ ์ฌ์ ๊ฐ ์์ ๋ ์ง์ฐ๋์ด ์
๋ฐ์ดํธ๋ผ์.
์ด๋ก ์ธํด ์ฌ์ฉ์๊ฐ input์ ํ์ดํํ ๋ UI๊ฐ ๋๊ธฐ์ง ์๊ณ ๋ถ๋๋ฝ๊ฒ ์๋ํ๋ฉฐ, ๋ฌด๊ฑฐ์ด ๋ฆฌ์คํธ ๋ ๋๋ง์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌ๋ ์ ์์ด์.
2๏ธโฃ useTransition๊ณผ์ ์ฐจ์ด์
useTransition๊ณผ useDeferredValue๋ ๋ชจ๋ "๊ธด๊ธํ์ง ์์" ์
๋ฐ์ดํธ๋ฅผ ์ฒ๋ฆฌํ์ฌ UI ์๋ต์ฑ์ ๊ฐ์ ํ๋ค๋ ๊ณตํต์ ์ด ์์ง๋ง, ์ฌ์ฉ ๋ฐฉ์๊ณผ ๋ชฉ์ ์์ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ด์.
์ ๋ณด
useTransition:
- ํน์ ์ํ ์ ๋ฐ์ดํธ ๋ก์ง ์์ฒด๋ฅผ "๊ธด๊ธํ์ง ์์" ๊ฒ์ผ๋ก ํ์ํ ๋ ์ฌ์ฉํด์.
- ์ฃผ๋ก ์ํ๋ฅผ ์ง์ ๋ณ๊ฒฝํ๋ ํจ์(
setState๋ฑ)๋ฅผ ๋ํํ ๋ ์ฌ์ฉํด์.
isPending๊ฐ์ ํตํด ์ ๋ฐ์ดํธ ์งํ ์ฌ๋ถ๋ฅผ ๋ช ์์ ์ผ๋ก ์ ์ ์์ด์.
- ์์: ๊ฒ์์ด ์ ๋ ฅ ์input๊ฐ์ ์ฆ์ ์ ๋ฐ์ดํธํ๊ณ , ๊ฒ์ ๊ฒฐ๊ณผ ๋ชฉ๋ก ๋ ๋๋ง์startTransition์ผ๋ก ๋ฌถ์ด ์ง์ฐ ์ฒ๋ฆฌํ ๋.
์ ๋ณด
useDeferredValue:
- ์ด๋ค "๊ฐ" ์์ฒด๋ฅผ ์ง์ฐ๋ ๋ฒ์ ์ผ๋ก ๋ง๋ค๊ณ ์ถ์ ๋ ์ฌ์ฉํด์.
- ์ฃผ๋ก ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ๋ด๋ ค๋ฐ์
props๋ ํ์ฌ ์ํ ๊ฐ์ ์ฐ์ฐ์ด ๋ฌด๊ฑฐ์ด ์์ ์ปดํฌ๋ํธ์ ์ ๋ฌ๋ ๋ ์ ์ฉํด์.
isPending๊ณผ ๊ฐ์ ๋ช ์์ ์ธ ๋ก๋ฉ ์ํ๋ฅผ ์ ๊ณตํ์ง ์์ง๋ง,value !== deferredValue์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ํ ๋ณํ๋ฅผ ๊ฐ์งํ ์ ์์ด์.
- ์์:input์ ์ ๋ ฅ๋ ๊ฒ์์ด ๊ฐ ์์ฒด๋ฅผuseDeferredValue๋ก ๊ฐ์ธ์, ๋ฌด๊ฑฐ์ด ํํฐ๋ง ๋ก์ง์ด ์ด ์ง์ฐ๋ ๊ฐ์ ์ฌ์ฉํ๋๋ก ํ ๋.
๊ฐ๋จํ ๋งํด, useTransition์ "์ด๋ค ์ก์
(์ํ ๋ณ๊ฒฝ)์ ์ง์ฐ์ํฌ๊น?"์ ์ด์ ์ ๋ง์ถ๊ณ , useDeferredValue๋ "์ด๋ค ๊ฐ์ ์ง์ฐ์ํฌ๊น?"์ ์ด์ ์ ๋ง์ถ๋ค๊ณ ์ดํดํ์๋ฉด ์ข์์.
๐ ๏ธ ์ค์ ์์ ๋ก ์ดํดํ๋ ์ต์ ํ
์ด์ ์ค์ ์๋๋ฆฌ์ค์์ ๋ ํ
์ ์ด๋ป๊ฒ ์ ์ฉํ ์ ์๋์ง ๊ตฌ์ฒด์ ์ธ ์์๋ฅผ ํตํด ์ดํด๋ณผ๊ฒ์.
๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ ํํฐ๋งํ๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ ํด๋ณผ๊ฒ์.
0๏ธโฃ ๊ฒ์ ํํฐ๋ง ์ปดํฌ๋ํธ ์ต์ ํ
์ฌ์ฉ์๊ฐ ๊ฒ์ input์ ํ์ดํํ ๋๋ง๋ค ์๋ง ๊ฐ์ ์์ดํ
์ ํํฐ๋งํ๊ณ ๋ ๋๋งํด์ผ ํ๋ค๊ณ ์์ํด ๋ณด์ธ์.
์ด๋ฌํ ์ํฉ์์ input์ ์๋ต์ฑ๊ณผ ํํฐ๋ง ๊ฒฐ๊ณผ์ ์
๋ฐ์ดํธ ๊ฐ์ ๊ท ํ์ ๋ง์ถ๋ ๊ฒ์ด ์ค์ํด์.
// HeavyFilterList.tsx import React from "react"; interface HeavyFilterListProps { items: string[]; filterTerm: string; } // ์ด ์ปดํฌ๋ํธ๋ filterTerm์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ๋ฌด๊ฑฐ์ด ํํฐ๋ง ๋ฐ ๋ ๋๋ง ์์ ์ ์ํํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. const HeavyFilterList = ({ items, filterTerm }: HeavyFilterListProps) => { // ์๋์ ์ธ ์ง์ฐ ์ถ๊ฐ (์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋ณต์กํ ๊ณ์ฐ์ด๋ ๋๋์ DOM ์กฐ์์ด ๋ ์ ์์ด์) const startTime = performance.now(); while (performance.now() - startTime < 10) { /* Simulate heavy work */ } const filteredItems = React.useMemo(() => { if (!filterTerm) return items.slice(0, 100); // ๊ฒ์์ด๊ฐ ์์ผ๋ฉด ์ด๊ธฐ 100๊ฐ๋ง ๋ณด์ฌ์ค return items .filter((item) => item.toLowerCase().includes(filterTerm.toLowerCase())) .slice(0, 1000); // ์ต๋ 1000๊ฐ๊น์ง๋ง ๋ณด์ฌ์ค }, [items, filterTerm]); return ( <div style={{ border: "1px solid #eee", padding: "10px", marginTop: "10px", maxHeight: "300px", overflowY: "auto", }} > <h3>ํํฐ๋ง๋ ๊ฒฐ๊ณผ ({filteredItems.length}๊ฐ)</h3> {filteredItems.length === 0 && <p>๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ด์.</p>} <ul> {filteredItems.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> </div> ); }; export default HeavyFilterList;
์ HeavyFilterList ์ปดํฌ๋ํธ๋ filterTerm์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค useMemo๋ฅผ ์ฌ์ฉํ์ฌ ํํฐ๋ง ์์
์ ์ํํด์.
useMemo๋ฅผ ์ฌ์ฉํ์์๋ ๋ถ๊ตฌํ๊ณ , ํํฐ๋ง ๋์ items์ ์๊ฐ ๋งค์ฐ ๋ง๊ฑฐ๋ filterTerm์ด ์์ฃผ ๋ณ๊ฒฝ๋๋ฉด ์ฌ์ ํ UI ๋ธ๋กํน์ด ๋ฐ์ํ ์ ์๋ต๋๋ค.
1๏ธโฃ useTransition ์ ์ฉ ์์
์ด์ ์ด ๊ฒ์ ํํฐ๋ง ์ปดํฌ๋ํธ์ useTransition์ ์ ์ฉํ์ฌ input์ ์๋ต์ฑ์ ๊ฐ์ ํด๋ณผ๊ฒ์.
import React, { useState, useTransition } from "react"; import HeavyFilterList from "./HeavyFilterList"; // ์์์ ์ ์ํ ์ปดํฌ๋ํธ const allItems = Array.from( { length: 50000 }, (_, i) => `Product ${i + 1} - A very detailed description for item ${i + 1}`, ); const SearchWithTransition = () => { const [inputValue, setInputValue] = useState(""); // input์ ์ฆ์ ๋ฐ์๋ ๊ฐ const [filterQuery, setFilterQuery] = useState(""); // HeavyFilterList์ ์ ๋ฌ๋ ๊ฐ const [isSearching, startTransition] = useTransition(); // useTransition ํ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); // input ๊ฐ์ ์ฆ์ ์ ๋ฐ์ดํธ // ๊ธด๊ธํ์ง ์์ ์ ๋ฐ์ดํธ๋ก ํํฐ๋ง ์ฟผ๋ฆฌ ์ ๋ฐ์ดํธ๋ฅผ ๋ํ startTransition(() => { setFilterQuery(e.target.value); }); }; return ( <div> <h2>๐ `useTransition`์ ์ด์ฉํ ๊ฒ์ ํํฐ๋ง</h2> <input type="text" value={inputValue} onChange={handleInputChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์..." style={{ width: "300px", padding: "8px", fontSize: "16px" }} /> {isSearching && ( <p style={{ color: "blue", display: "inline-block", marginLeft: "10px" }} > ๊ฒ์ ์ค... </p> )} <p>ํ์ฌ ์ ๋ ฅ: **{inputValue}**</p> <HeavyFilterList items={allItems} filterTerm={filterQuery} /> </div> ); }; export default SearchWithTransition;
SearchWithTransition ์ปดํฌ๋ํธ์์๋ inputValue๋ ์ฌ์ฉ์์ ํ์ดํ์ ๋ง์ถฐ ์ฆ์ ์
๋ฐ์ดํธ๋์ด input ํ๋์ ๋ฐ์๋ผ์.
๋ฐ๋ฉด, HeavyFilterList์ ์ ๋ฌ๋๋ filterQuery๋ startTransition์ผ๋ก ๊ฐ์ธ์ ธ ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ๋ก ์ฒ๋ฆฌ๋ผ์.
๋ฐ๋ผ์ ์ฌ์ฉ์๋ input์ ํ์ดํํ๋ ๋์์๋ UI๊ฐ ๋๊น ์์ด ๋ถ๋๋ฝ๊ฒ ๋ฐ์ํ๋ ๊ฒ์ ๊ฒฝํํ ์ ์๊ณ , isSearching ์ํ๋ฅผ ํตํด ๊ฒ์ ์ค์์ ์ธ์งํ ์ ์๊ฒ ๋๋ต๋๋ค.
2๏ธโฃ useDeferredValue ์ ์ฉ ์์
์ด๋ฒ์๋ ๋์ผํ ๊ฒ์ ํํฐ๋ง ์๋๋ฆฌ์ค์ useDeferredValue๋ฅผ ์ ์ฉํด๋ณผ๊ฒ์.
import React, { useState, useDeferredValue } from "react"; import HeavyFilterList from "./HeavyFilterList"; // ์์์ ์ ์ํ ์ปดํฌ๋ํธ const allItems = Array.from( { length: 50000 }, (_, i) => `Product ${i + 1} - A very detailed description for item ${i + 1}`, ); const SearchWithDeferredValue = () => { const [inputValue, setInputValue] = useState(""); // input์ ์ฆ์ ๋ฐ์๋ ๊ฐ // inputValue์ ์ง์ฐ๋ ๋ฒ์ ์ ์์ฑ. HeavyFilterList๋ ์ด ๊ฐ์ ์ฌ์ฉ. const deferredInputValue = useDeferredValue(inputValue); const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); }; return ( <div> <h2>๐ก `useDeferredValue`๋ฅผ ์ด์ฉํ ๊ฒ์ ํํฐ๋ง</h2> <input type="text" value={inputValue} onChange={handleInputChange} placeholder="๊ฒ์์ด๋ฅผ ์ ๋ ฅํ์ธ์..." style={{ width: "300px", padding: "8px", fontSize: "16px" }} /> {inputValue !== deferredInputValue && ( <p style={{ color: "orange", display: "inline-block", marginLeft: "10px", }} > ๊ฒฐ๊ณผ ์ ๋ฐ์ดํธ ๋๊ธฐ ์ค... </p> )} <p>ํ์ฌ ์ ๋ ฅ: **{inputValue}**</p> {/* HeavyFilterList๋ ์ง์ฐ๋ ๊ฐ์ ์ฌ์ฉํ์ฌ ๋ ๋๋ง๋ผ์ */} <HeavyFilterList items={allItems} filterTerm={deferredInputValue} /> </div> ); }; export default SearchWithDeferredValue;
SearchWithDeferredValue ์ปดํฌ๋ํธ์์๋ inputValue๋ input์ ์ฆ์ ๋ฐ์๋์ง๋ง, HeavyFilterList์ ์ ๋ฌ๋๋ filterTerm์ผ๋ก๋ useDeferredValue(inputValue)๋ฅผ ํตํด ์์ฑ๋ deferredInputValue๋ฅผ ์ฌ์ฉํด์.
์ด ๋ฐฉ์ ์ญ์ useTransition๊ณผ ์ ์ฌํ๊ฒ input์ ์๋ต์ฑ์ ํ๋ณดํ๋ฉด์ ๋ฌด๊ฑฐ์ด ํํฐ๋ง ์์
์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค์.
inputValue !== deferredInputValue ์กฐ๊ฑด์ ํตํด ์ง์ฐ๋ ์
๋ฐ์ดํธ๊ฐ ์งํ ์ค์์ ์ฌ์ฉ์์๊ฒ ์๋ฆด ์๋ ์๋ต๋๋ค.
๐ ํต์ฌ ์์ฝ ๋ฐ ๋ค์ ๋จ๊ณ
0๏ธโฃ useTransition๊ณผ useDeferredValue์ ์ญํ
๋ ํ
๋ชจ๋ React ์ ํ๋ฆฌ์ผ์ด์
์ UI ์๋ต์ฑ์ ํฌ๊ฒ ํฅ์์ํค๋ ๋ฐ ๊ธฐ์ฌํด์.
ํต์ฌ์ ๊ธด๊ธํ ์
๋ฐ์ดํธ(์ฌ์ฉ์ ์
๋ ฅ)์ ๊ธด๊ธํ์ง ์์ ์
๋ฐ์ดํธ(๋ฌด๊ฑฐ์ด ๋ ๋๋ง)๋ฅผ ๋ถ๋ฆฌํ์ฌ ์ฒ๋ฆฌํจ์ผ๋ก์จ, ์ฌ์ฉ์๊ฐ ์ฑ์ด "๋ฉ์ท๋ค"๊ณ ๋๋ผ์ง ์๋๋ก ํ๋ ๊ฒ์ด์ฃ .
useTransition: ํน์ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ UI๋ฅผ ๋ธ๋กํนํ์ง ์๋๋ก ํด์. ์ฃผ๋ก "์ก์ "์ ๋ํํ ๋ ์ฌ์ฉํ๊ณ ,isPending์ผ๋ก ๋ก๋ฉ ์ํ๋ฅผ ๋ช ํํ ์ ์ ์์ด์.useDeferredValue: ์ด๋ค ๊ฐ ์์ฒด๋ฅผ ์ง์ฐ๋ ๋ฒ์ ์ผ๋ก ์ ๊ณตํ์ฌ, ํด๋น ๊ฐ์ ์ฌ์ฉํ๋ ๋ฌด๊ฑฐ์ด ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ ์ง์ฐ์์ผ์. ์ฃผ๋กprops๋ ์ํ ๊ฐ์ ์ ์ฉํ์ฌ ์์ ์ปดํฌ๋ํธ์ ๋ ๋๋ง ์ต์ ํ์ ์ฌ์ฉ๋ผ์.
1๏ธโฃ ์ธ์ ์ด๋ค ํ ์ ์ฌ์ฉํด์ผ ํ ๊น์?
์ ์ฉํ ํ
useTransition์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ:
- ์ฌ์ฉ์ ์ธํฐ๋์ (ํด๋ฆญ, ํผ ์ ์ถ ๋ฑ)์ผ๋ก ์ธํด ๋ฐ์ํ๋ ์ํ ์ ๋ฐ์ดํธ๊ฐ ๋ฌด๊ฑฐ์ด ๋ ๋๋ง์ ์ ๋ฐํ ๋.
- ํน์ "์ก์ "์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฒ๋ฆฌํ๋ฉด์, ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํด์ผ ํ ๋.
-isPending์ํ๋ฅผ ํตํด ๋ช ํํ ๋ก๋ฉ UI๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ ๋.
์ ์ฉํ ํ
useDeferredValue๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ:
- ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ์ ๋ฌ๋ฐ์
props๋ ๋ก์ปฌ ์ํ ๊ฐ์ด ๋ฌด๊ฑฐ์ด ์์ ์ปดํฌ๋ํธ์ ๋ ๋๋ง์ ์ ๋ฐํ ๋.
- ์ฃผ๋ก
inputํ๋์ ๊ฐ๊ณผ ๊ฐ์ด ์์ฃผ ๋ณ๊ฒฝ๋๋ ๊ฐ์ด์ง๋ง, ์ด ๊ฐ์ ์ฌ์ฉํ๋ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ์ง์ฐ๋์ด๋ ๊ด์ฐฎ์ ๋.
-useTransition์ฒ๋ผ ๋ช ์์ ์ธstartTransitionํธ์ถ ์์ด ๊ฐ์ ๋ณํ๋ฅผ React์ ๋งก๊ธฐ๊ณ ์ถ์ ๋.
๋ ํ
์ ์ํธ ๋ณด์์ ์ผ๋ก ์ฌ์ฉ๋ ์ ์์ผ๋ฉฐ, ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์
์ ํน์ฑ๊ณผ ์ฑ๋ฅ ๋ณ๋ชฉ ์ง์ ์ ๋ฐ๋ผ ์ ์ ํ ์ ํํ์ฌ ์ ์ฉํ๋ ๊ฒ์ด ์ค์ํด์.
React์ ๋์์ฑ ๊ธฐ๋ฅ๋ค์ ์ ์ดํดํ๊ณ ํ์ฉํ๋ฉด, ์ฌ์ฉ์์๊ฒ ํจ์ฌ ๋ ๋ถ๋๋ฝ๊ณ ์พ์ ํ ๊ฒฝํ์ ์ ๊ณตํ ์ ์๋ต๋๋ค.
์ค๋ ํฌ์คํ
์ด ์ฌ๋ฌ๋ถ์ React ์ ํ๋ฆฌ์ผ์ด์
์ฑ๋ฅ ์ต์ ํ์ ์ค์ง์ ์ธ ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋ผ์.
๊ถ๊ธํ ์ ์ด๋ ๋ ๋ค๋ฃจ๊ณ ์ถ์ ์ฃผ์ ๊ฐ ์๋ค๋ฉด ์ธ์ ๋ ์ง ๋๊ธ๋ก ์๋ ค์ฃผ์ธ์!
๐ฎ ์ฐธ๊ณ
์ฐ๊ด๋ ํฌ์คํธ
- ๋จ์ด: 1,996๊ฐ22๋ถ
[๐ค] Next.js App Router์ Auth.js (NextAuth.js) ์ฐ๋ ๊ฐ์ด๋: ์๋ฒ ๋ฐ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ์ธ์ ๊ด๋ฆฌ
Next.js 14+ App Router ํ๊ฒฝ์์ Auth.js (๊ตฌ NextAuth.js)๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํ๊ณ ํจ์จ์ ์ธ ์ธ์ฆ ์์คํ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์ธ์ ์ ๊ด๋ฆฌํ๊ณ ๋ณดํธํ๋ ์ค์ฉ์ ์ธ ํ๊ณผ ์ฝ๋ ์์๋ฅผ ์ ๊ณตํด์.
- ๋จ์ด: 2,440๊ฐ26๋ถ
[๐ค] Next.js App Router์์ Zod์ TypeScript๋ก API ์ ํจ์ฑ ๊ฒ์ฌ ๋ฐ ํ์ ์์ ์ฑ ํ๋ณด
Next.js App Router ํ๊ฒฝ์์ Zod์ TypeScript๋ฅผ ํ์ฉํ์ฌ API ์์ฒญ ๋ฐ์ดํฐ์ ๋ฐํ์ ์ ํจ์ฑ์ ๊ฒ์ฌํ๊ณ , ๋์์ ๊ฐ๋ ฅํ ํ์ ์์ ์ฑ์ ํ๋ณดํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,646๊ฐ18๋ถ
[๐ค] JavaScript ๊ตฌ์กฐ ๋ถํด ํ ๋น ํจํด ์ฌํ: ๋ค์ํ ํ์ฉ๊ณผ ์ค์ ํ
JavaScript ๊ตฌ์กฐ ๋ถํด ํ ๋น(Destructuring Assignment)์ ๊ธฐ๋ณธ๋ถํฐ ์ค์ฒฉ ๊ฐ์ฒด, ๊ธฐ๋ณธ๊ฐ, Rest ๋ฌธ๋ฒ, ํจ์ ์ธ์ ํ์ฉ๊น์ง ๋ค์ํ ํจํด๊ณผ ์ค์ ํ์ ํตํด ์ฝ๋๋ฅผ ๋ ๊ฐ๊ฒฐํ๊ณ ํจ์จ์ ์ผ๋ก ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,841๊ฐ23๋ถ
[๐ค] ์น ์ฑ๋ฅ ์ต์ ํ: ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ก Core Web Vitals ์ง๋จํ๊ณ ๊ฐ์ ํ๊ธฐ
๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ(DevTools)์ Lighthouse, Performance, Network ํญ์ ํ์ฉํ์ฌ Core Web Vitals๋ฅผ ์ง๋จํ๊ณ ์ค์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ๊ฐ์ ํ๋ ์ค์ง์ ์ธ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,957๊ฐ22๋ถ
[๐ค] TypeScript ํจ์ ์ค๋ฒ๋ก๋ฉ: ์๊ทธ๋์ฒ์ ๊ตฌํ์ฒด๋ฅผ ์๋ฒฝํ๊ฒ ์ดํดํ๊ณ ํ์ฉํ๊ธฐ
TypeScript์์ ํจ์ ์ค๋ฒ๋ก๋ฉ์ ์ฌ์ฉํ์ฌ ๋ค์ํ ํ์ ์ ์ธ์๋ฅผ ์ฒ๋ฆฌํ๊ณ , ์ฝ๋์ ๊ฐ๋ ์ฑ ๋ฐ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ์๊ทธ๋์ฒ์ ๊ตฌํ์ฒด์ ๊ด๊ณ๋ฅผ ์ค์ฌ์ผ๋ก ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 1,709๊ฐ19๋ถ
[๐ค] JavaScript requestIdleCallback๊ณผ isInputPending์ผ๋ก UI ๋ฐ์์ฑ ๊ฐ์ ํ๊ธฐ: ๋ฉ์ธ ์ค๋ ๋ ์ต์ ํ ์ฌํ
JavaScript์ `requestIdleCallback`๊ณผ `isInputPending` API๋ฅผ ํ์ฉํ์ฌ ๋ฉ์ธ ์ค๋ ๋ ๋ธ๋กํน์ ์ค์ด๊ณ UI ๋ฐ์์ฑ์ ๊ทน๋ํํ๋ ์ค์ง์ ์ธ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ์ฌ์ฉ์ ๊ฒฝํ์ ์ํ ์น ์ฑ๋ฅ ์ต์ ํ ์ ๋ต์ ์๋ดํด ๋๋ ค์.
- ๋จ์ด: 2,149๊ฐ23๋ถ
[๐ค] Next.js App Router์ `generateMetadata`์ `generateViewport` ์ฌํ: SEO ๋ฐ PWA ์ต์ ํ ์ ๋ต
Next.js 14/13 App Router์์ `generateMetadata`์ `generateViewport` ํจ์๋ฅผ ํ์ฉํ์ฌ ๋์ SEO ๋ฉํํ๊ทธ์ PWA ์ค์ ์ ์ต์ ํํ๋ ์ฌํ ์ ๋ต์ ๋ค๋ฃน๋๋ค. ์ค๋ฌด ์์์ ํจ๊ป ์น์ฌ์ดํธ์ ๊ฒ์ ์์ง ๋ ธ์ถ ๋ฐ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,788๊ฐ19๋ถ
[๐ค] CSS ๋ณ์(Custom Properties) ์ฌํ: ๋์ ํ ๋ง, ์ค์ฝํ, JavaScript ํ์ฉ ์ ๋ต
CSS ๋ณ์(Custom Properties)์ ๊ธฐ๋ณธ๋ถํฐ ๋์ ํ ๋ง ๊ตฌํ, ์ค์ฝํ ์ ๋ต, ๊ทธ๋ฆฌ๊ณ JavaScript์์ ์ํธ์์ฉ๊น์ง ์ค๋ฌด์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ์ฌํ ํ์ฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,293๊ฐ15๋ถ
[๐ค] Next.js App Router Edge Runtime: ๊ธ๋ก๋ฒ ์ฑ๋ฅ๊ณผ ๋น์ฉ ํจ์จ์ฑ ๊ทน๋ํ ์ ๋ต
Next.js App Router์์ Edge Runtime์ ํ์ฉํด Route Handler ์ฑ๋ฅ์ ๊ทน๋ํํ๊ณ , ๊ธ๋ก๋ฒ ์ฌ์ฉ์์๊ฒ ๋น ๋ฅธ ์๋ต ์๋๋ฅผ ์ ๊ณตํ๋ฉฐ, ๋น์ฉ ํจ์จ์ ์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด์ํ๋ ์ค์ ์ ๋ต์ ์์ธํ ๋ค๋ค์. Edge Runtime์ ์ฅ์ ๊ณผ ์ ํ์ฌํญ, ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฝ๋ ์์๋ฅผ ํตํด ํจ๊ณผ์ ์ธ ์ ์ฉ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,980๊ฐ25๋ถ
[๐ค] Next.js/React์์ ํด๋ฆฐ ์ํคํ ์ฒ์ ์์กด์ฑ ์ญ์ ์์น ์ ์ฉํ๊ธฐ: ํ์ฅ ๊ฐ๋ฅํ ํ๋ฐํธ์๋ ๋ง๋ค๊ธฐ
Next.js์ React ํ๋ก์ ํธ์์ ํด๋ฆฐ ์ํคํ ์ฒ์ ์์กด์ฑ ์ญ์ ์์น(DIP)์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. ๋๋ฉ์ธ, ์ ์ค์ผ์ด์ค, ์ธํ๋ผ ๊ณ์ธต์ ๋ถ๋ฆฌํ์ฌ ์ ์ง๋ณด์์ฑ๊ณผ ํ ์คํธ ์ฉ์ด์ฑ์ ๊ทน๋ํํ๋ ์ค์ฉ์ ์ธ ์ ๋ต์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,733๊ฐ19๋ถ
[๐ค] Jotai๋ก Next.js App Router ํด๋ผ์ด์ธํธ ์ํ ๊ด๋ฆฌ ์ฌํ: ์ํฐ ํจํด๊ณผ ์ต์ ํ
Next.js App Router ํ๊ฒฝ์์ Jotai ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ํจ์จ์ ์ธ ํด๋ผ์ด์ธํธ ์ํ ๊ด๋ฆฌ ์ ๋ต์ ์์๋ณด์ธ์. ์ํฐ ํจํด์ ์ด์ฉํ ์ํ ๋ถ๋ฆฌ, ์ต์ ํ ๊ธฐ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค์ฉ์ ์ธ ์ฝ๋ ์์๋ฅผ ํตํด ๋ณต์กํ UI ์ํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,720๊ฐ18๋ถ
[๐ค] Next.js App Router ์ ์ญ ์ํ ๊ด๋ฆฌ: Server/Client Components ์๋ฒฝ ๊ฐ์ด๋
Next.js App Router ํ๊ฒฝ์์ Server Components์ Client Components ๊ฐ์ ์ ์ญ ์ํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ํ๊ตฌํด์. Zustand, Recoil, Context API ๋ฑ ๋ค์ํ ์๋ฃจ์ ๊ณผ ์ค์ฉ์ ์ธ ์ ๋ต์ ํตํด ๋ณต์กํ ์ํ ๊ด๋ฆฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ๋ณด์ธ์.
- ๋จ์ด: 1,787๊ฐ20๋ถ
[๐ค] Atomic Design ํจํด์ผ๋ก React/Next.js ์ปดํฌ๋ํธ ์ํคํ ์ฒ ํ์ฅ์ฑ ๋์ด๊ธฐ
React ๋ฐ Next.js ํ๋ก์ ํธ์์ Atomic Design ํจํด์ ์ ์ฉํ์ฌ ์ปดํฌ๋ํธ์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ํ์ฅ์ฑ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์๋ ค๋๋ ค์. ํจ์จ์ ์ธ ํ๋ก ํธ์๋ ์ํคํ ์ฒ๋ฅผ ๊ตฌ์ถํด ๋ณด์ธ์.
- ๋จ์ด: 2,160๊ฐ24๋ถ
[๐ค] ์๋ฐ์คํฌ๋ฆฝํธ Web Workers: UI ๋ธ๋กํน ์์ด ๋ฌด๊ฑฐ์ด ์์ ์ฒ๋ฆฌํ๊ธฐ
ํ๋ก ํธ์๋์์ CPU ์ง์ฝ์ ์ธ ์์ ์ UI ๋ธ๋กํน์ ํํ ๋ฌธ์ ์์. ์๋ฐ์คํฌ๋ฆฝํธ Web Workers๋ก ๋ฉ์ธ ์ค๋ ๋๋ฅผ ํด๋ฐฉํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ์ต์ ํํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์ฝ๋ ์์์ ํจ๊ป ์์๋ณด์ธ์. ๋ณต์กํ ๊ณ์ฐ, ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฑ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์ ํ๊ฒ ์คํํ๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
- ๋จ์ด: 1,822๊ฐ20๋ถ
[๐ค] TypeScript `as const` ์ฌ์ธต ๋ถ์: ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ๊ณผ ๋ถ๋ณ์ฑ ํ์ฉ ์ ๋ต
TypeScript์ `as const`๋ฅผ ํ์ฉํ์ฌ ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ์ ๊ฐํํ๊ณ , ๋ฐฐ์ด๊ณผ ๊ฐ์ฒด์ ๋ถ๋ณ์ฑ์ ์์ ํ๊ฒ ํ๋ณดํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์ค๋ฌด ์ฝ๋์ ํ์ ์์ ์ฑ๊ณผ ๊ฐ๋ ์ฑ์ ๋์ฌ๋ณด์ธ์.
- ๋จ์ด: 1,486๊ฐ17๋ถ
[๐ค] Next.js @next/font๋ฅผ ํ์ฉํ ํฐํธ ์ต์ ํ ๋ฐ CLS ๊ฐ์ ์ ๋ต
Next.js ์ ํ๋ฆฌ์ผ์ด์ ์์ ํฐํธ ๋ก๋ฉ์ผ๋ก ์ธํ Cumulative Layout Shift(CLS)๋ฅผ ์ต์ํํ๊ณ ์น ์ฑ๋ฅ์ ๊ทน๋ํํ๋ @next/font ์ฌ์ฉ๋ฒ๊ณผ ์ค์ฉ์ ์ธ ์ต์ ํ ์ ๋ต์ ์์ธํ ์์๋ด์.
- ๋จ์ด: 1,914๊ฐ21๋ถ
[๐ค] Next.js App Router ๊ตญ์ ํ (i18n) ์๋ฒฝ ๊ฐ์ด๋: ๋ค๊ตญ์ด ์ง์ ๊ตฌํ ์ ๋ต
Next.js 14+ App Router ํ๊ฒฝ์์ ๊ตญ์ ํ(i18n)๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์๋ ค๋๋ ค์. ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํ ์ธ์ด ๊ฐ์ง๋ถํฐ ํด๋ผ์ด์ธํธ/์๋ฒ ์ปดํฌ๋ํธ์์์ ๋ค๊ตญ์ด ํ ์คํธ ์ฒ๋ฆฌ๊น์ง, ์ค๋ฌด์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๋ค๊ตญ์ด ์ง์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ดํด ๋๋ฆด๊ฒ์.
- ๋จ์ด: 1,328๊ฐ16๋ถ
[์น ์ฑ๋ฅ ์ต์ ํ] Interaction to Next Paint (INP) ๊ฐ์ : ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ์ค์ ์ ๋ต
์๋ก์ด Core Web Vitals ์งํ, Interaction to Next Paint(INP)๋ฅผ ๊น์ด ์ดํดํ๊ณ ์ค์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ INP ์ ์๋ฅผ ๊ฐ์ ํ๋ ์ค์ฉ์ ์ธ ์ต์ ํ ์ ๋ต๋ค์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์. ์ฌ์ฉ์ ์ธํฐ๋์ ์๋ต์ฑ์ ๋์ฌ ์ง์ ํ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ง๋ค์ด ๋ณด์ธ์.
๋จ์ด: 1,668๊ฐ20๋ถ[๐ค] JavaScript Decorator ์ฌ์ธต ๋ถ์: ๋ฉํํ๋ก๊ทธ๋๋ฐ๊ณผ ํ์ ์คํฌ๋ฆฝํธ ํ์ฉ ์ ๋ต
JavaScript Decorator์ ๊ฐ๋ ๋ถํฐ ํ์ ์คํฌ๋ฆฝํธ์์์ ํ์ฉ, ๊ทธ๋ฆฌ๊ณ ๋ฏธ๋ ํ์คํ ๋ฐฉํฅ๊น์ง ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ๋ฉฐ, ์ค๋ฌด์์ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ๊ณผ ๊ฐ๋ ์ฑ์ ๋์ด๋ ๋ฉํํ๋ก๊ทธ๋๋ฐ ํจํด์ ํ์ตํด ๋ณด์ธ์.
- ๋จ์ด: 2,634๊ฐ28๋ถ
[๐ค] JavaScript ํ๋ก๋ฏธ์ค/async-await ์ฌํ: ์ค์ ํจํด๊ณผ ์ฃผ์์ฌํญ
JavaScript์ ํ๋ก๋ฏธ์ค์ async/await๋ฅผ ๊น์ด ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ง์ฃผํ๋ ๋ณต์กํ ๋น๋๊ธฐ ์ฒ๋ฆฌ ์ํฉ์์ ํ์ฉํ ์ ์๋ ๊ณ ๊ธ ํจํด๊ณผ ํํ ๋ฐ์ํ๋ ์ค์๋ฅผ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,421๊ฐ16๋ถ
[๐ค] TypeScript 5.2+ `using` ์ ์ธ์ผ๋ก ์์ ์๋ ๊ด๋ฆฌํ๊ธฐ: ๊น๋ํ ๋ฆฌ์์ค ์ ๋ฆฌ์ ์์
TypeScript 5.2์์ ๋์ ๋ `using` ์ ์ธ์ ํ์ฉํ์ฌ ํ์ผ ํธ๋ค, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ฑ ๋ค์ํ ์์์ ์๋์ผ๋ก ๊ด๋ฆฌํ๊ณ ๊น๋ํ๊ฒ ์ ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์ ์ ํจ๊ป ์์ธํ ์์๋ด์. ๋ ์ด์ `try...finally` ๋ธ๋ก์ผ๋ก ๋ณต์กํ๊ฒ ์์์ ํด์ ํ ํ์ ์์ด์.
- ๋จ์ด: 2,006๊ฐ22๋ถ
[๐ค] Feature-Sliced Design (FSD)์ผ๋ก ํ์ฅ ๊ฐ๋ฅํ ํ๋ก ํธ์๋ ์ํคํ ์ฒ ๊ตฌ์ถํ๊ธฐ
Feature-Sliced Design (FSD)์ ๋๊ท๋ชจ ํ๋ก ํธ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ด๋ ๊ฐ๋ ฅํ ์ํคํ ์ฒ ํจํด์ด์์. FSD์ ํต์ฌ ์์น, ๋ ์ด์ด ๊ตฌ์กฐ, ์ค๋ฌด ์ ์ฉ ๋ฐฉ๋ฒ์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
- ๋จ์ด: 1,781๊ฐ21๋ถ
[๐ค] TypeScript `moduleResolution: 'bundler'`์ `verbatimModuleSyntax` ์๋ฒฝ ์ดํด: ๋ชจ๋ ๋ฒ๋ค๋ฌ ์๋์ ํ์ ์คํฌ๋ฆฝํธ ์ค์ ์ต์ ํ
๋ชจ๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ๋ก์ ํธ์์ TypeScript์ `moduleResolution: 'bundler'`์ `verbatimModuleSyntax` ์ต์ ์ด ์ ์ค์ํ์ง, ์ด๋ป๊ฒ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ํ๊ณ ํ์ฉํ์ฌ ๊ฐ๋ฐ ๊ฒฝํ๊ณผ ๋ฒ๋ค๋ง ํจ์จ์ ๊ทน๋ํํ๋์ง ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
- ๋จ์ด: 1,908๊ฐ24๋ถ
[๐ค] ๋ชจ๋ ธ๋ ํฌ ์ํคํ ์ฒ: ํจ์จ์ ์ธ ๋๊ท๋ชจ ํ๋ก์ ํธ ์ค๊ณ๋ฅผ ์ํ ์ค์ ๊ฐ์ด๋
๋๊ท๋ชจ ์น ํ๋ก์ ํธ์์ ๋ชจ๋ ธ๋ ํฌ๋ฅผ ๋์ ํ๋ ์ด์ , ์ฅ๋จ์ , ๊ทธ๋ฆฌ๊ณ ํจ๊ณผ์ ์ธ ๊ตฌ์กฐ ์ค๊ณ ๋ฐ ์ค๋ฌด ์ ์ฉ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์. ํ ์์ฐ์ฑ ํฅ์๊ณผ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
๋จ์ด: 2,188๊ฐ26๋ถ[๐ค] ์น ์ฑ๋ฅ ์ต์ ํ: ๋ฆฌํ๋ก์ฐ์ ๋ฆฌํ์ธํธ ์ต์ํ ์ ๋ต
๋ธ๋ผ์ฐ์ ๋ ๋๋ง ๊ณผ์ ์ ํต์ฌ์ธ ๋ฆฌํ๋ก์ฐ(Reflow)์ ๋ฆฌํ์ธํธ(Repaint)์ ๋ฐ์ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ , ์ด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ค์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ๊ทน๋ํํ๋ ์ค์ง์ ์ธ ์ ๋ต๊ณผ ์ฝ๋ ์์๋ฅผ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,903๊ฐ19๋ถ
[๐ค] Vitest์ React Testing Library๋ก Next.js ์ปดํฌ๋ํธ ์๋ฒฝ ํ ์คํธํ๊ธฐ
Next.js ํ๋ก์ ํธ์์ Vitest์ React Testing Library๋ฅผ ํ์ฉํ์ฌ UI ์ปดํฌ๋ํธ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์ ์ ํจ๊ป ์์ธํ ์์๋ด์. ์ค์ ๋ถํฐ Mocking, ์ด๋ฒคํธ ์๋ฎฌ๋ ์ด์ ๊น์ง, ๊ฒฌ๊ณ ํ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ํ ํ ์คํธ ์ ๋ต์ ์ตํ๋ณด์ธ์.
- ๋จ์ด: 1,542๊ฐ20๋ถ
[๐ค] Tailwind CSS v4 ์ถ์: ๊ฐ๋ฐ์์๊ฒ ์ฐพ์์ฌ ๋ณํ์ ์ต์ ํ ์ ๋ต
Tailwind CSS v4์ ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ๊ณผ ์๋ก์ด ๊ธฐ๋ฅ๋ค์ ๊น์ด ์๊ฒ ๋ถ์ํ๊ณ , ์ค๋ฌด์์ ํจ์จ์ ์ผ๋ก ์ ์ฉํ๋ฉฐ ์ฑ๋ฅ์ ์ต์ ํํ๋ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,566๊ฐ20๋ถ
[๐ค] Next.js Dockerfile ์ต์ ํ: ํ๋ก๋์ ๋ฐฐํฌ๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋
Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ Docker ์ปจํ ์ด๋๋ก ํจ์จ์ ์ผ๋ก ๋ฐฐํฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ๋ฉํฐ์คํ ์ด์ง ๋น๋, ์บ์ฑ ์ ๋ต, ๋ณด์ ์ค์ ๋ฑ ํ๋ก๋์ ํ๊ฒฝ์ ์ต์ ํ๋ Dockerfile ์์ฑ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด ๋๋ ค์.
- ๋จ์ด: 2,177๊ฐ24๋ถ
[๐ค] CSS Grid ์ฌํ ๊ฐ์ด๋: ์ค์ ๋ ์ด์์ ํจํด๊ณผ ๋ฐ์ํ ๋์์ธ ์ ๋ต
CSS Grid๋ ๊ฐ๋ ฅํ 2์ฐจ์ ๋ ์ด์์ ์์คํ ์ด์์. ์ด ๊ฐ์ด๋์์ Grid์ ํต์ฌ ๊ฐ๋ ๋ถํฐ ์ค์ ๋ ์ด์์ ํจํด, ๋ฐ์ํ ๋์์ธ ์ ๋ต๊น์ง ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด ์ค๋ฌด์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋๋ก ๋์๋๋ ค์.
- ๋จ์ด: 1,718๊ฐ19๋ถ
[๐ค] TypeScript ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด ํ์ : ๋ฌธ์์ด ํ์ ์ ๋ง๋ฒ์ฌ๋ก ๋ณ์ ํ๊ธฐ
TypeScript์ ํ ํ๋ฆฟ ๋ฆฌํฐ๋ด ํ์ ์ ํ์ฉํ์ฌ ๋ณต์กํ ๋ฌธ์์ด ํจํด์ ์์ ํ๊ฒ ํ์ ์ถ๋ก ํ๊ณ , ๊ฐ๋ ฅํ ์ ํธ๋ฆฌํฐ ํ์ ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์ ์ ํจ๊ป ์์ธํ ์์๋ด์. ํ์ ์์ ์ฑ์ ํ ๋จ๊ณ ๋์ฌ ๊ฐ๋ฐ ๊ฒฝํ์ ๊ฐ์ ํด ๋ณด์ธ์.
- ๋จ์ด: 1,918๊ฐ23๋ถ
[๐ค] JavaScript WeakMap๊ณผ WeakSet: ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง์ ์ต์ ํ ์ ๋ต
JavaScript์์ WeakMap๊ณผ WeakSet์ ํ์ฉํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ๊ณ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ๊ฐ๋น์ง ์ปฌ๋ ์ ๋์ ์๋ฆฌ์ ํจ๊ป ๊ฐ์ฒด ์ฐธ์กฐ ๊ด๋ฆฌ์ ์ค์์ฑ์ ์ดํดํ๊ณ , ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ์ ๋ต์ ๋ฐฐ์๋ด์.
- ๋จ์ด: 1,455๊ฐ17๋ถ
[๐ค] Next.js/React ํ๋ก์ ํธ๋ฅผ ์ํ ESLint & Prettier ์ค์ ์๋ฒฝ ๊ฐ์ด๋
๋ณต์กํ Next.js ๋ฐ React ํ๋ก์ ํธ์์ ์ผ๊ด๋ ์ฝ๋ ์คํ์ผ๊ณผ ํ์ง์ ์ ์งํ๋ ESLint์ Prettier ์ค์ ๋ฐฉ๋ฒ์ ์์ธํ ์๋ ค๋๋ ค์. ํ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ต์ ํ๋ ์ค์ ์ผ๋ก ๊ฐ๋ฐ ํจ์จ์ ๋์ฌ๋ณด์ธ์.
- ๋จ์ด: 2,022๊ฐ25๋ถ
[๐ค] JavaScript์ ํต์ฌ: ํ๋กํ ํ์ ์ฒด์ธ ์๋ฒฝ ์ดํด์ ํ์ฉ ์ ๋ต
JavaScript์ ์ฌ์ฅ๋ถ, ํ๋กํ ํ์ ์ฒด์ธ์ ๋์ ์๋ฆฌ๋ฅผ ๊น์ด ํ๊ณ ๋ค์ด ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ๊ณผ ์์์ ์๋ฒฝํ๊ฒ ์ดํดํ๊ณ ์ค๋ฌด์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ์ฑ๋ฅ ์ต์ ํ ํ๋ ํจ๊ป ๋ค๋ค์.
- ๋จ์ด: 2,118๊ฐ22๋ถ
[๐ค] React ์ปค์คํ ํ : ์ฌ์ฌ์ฉ์ฑ ๋์ด๋ ์ค๊ณ ์์น๊ณผ ํ ์คํธ ์ ๋ต
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๊ทน๋ํํ๋ ์ปค์คํ ํ ์ค๊ณ ์์น, ์ค์ฉ์ ์ธ ํจํด, ๊ทธ๋ฆฌ๊ณ ๊ฒฌ๊ณ ํ ํ ์คํธ ์ ๋ต์ ์๋์ด ๊ฐ๋ฐ์์ ๊ด์ ์์ ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,107๊ฐ23๋ถ
[๐ค] React useRef ํ ์ฌ์ธต ๋ถ์: DOM ๋์ด์ ์ค์ ํ์ฉ ์ ๋ต
React useRef ํ ์ ๊ธฐ๋ณธ ์๋ฆฌ๋ถํฐ DOM ์์ ์ง์ ์ ์ด, ์ปดํฌ๋ํธ ๋ผ์ดํ์ฌ์ดํด ๊ด๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ๋ค์ํ ์ค์ ํ์ฉ ์ ๋ต๊น์ง ์ฌ์ธต์ ์ผ๋ก ๋ค๋ค์. ์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ useRef ์๋ฒฝ ๊ฐ์ด๋.
- ๋จ์ด: 1,762๊ฐ19๋ถ
[๐ค] Next.js 14/15์์ ๋์ OG ์ด๋ฏธ์ง ์์ฑ: ImageResponse ์๋ฒฝ ๊ฐ์ด๋
Next.js App Router ํ๊ฒฝ์์ ImageResponse๋ฅผ ํ์ฉํ์ฌ ๋์ OG ์ด๋ฏธ์ง๋ฅผ ํจ์จ์ ์ผ๋ก ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. SEO์ ์์ ๊ณต์ ์ต์ ํ๋ฅผ ์ํ ์ค์ ๊ฐ์ด๋์ ๋๋ค.
- ๋จ์ด: 1,481๊ฐ18๋ถ
[๐ค] Git ๋ธ๋์น ์ ๋ต: Git Flow vs GitHub Flow, ์ค๋ฌด์์ ์ด๋ป๊ฒ ์ ํํ๊ณ ์ด์ํ ๊น์?
๊ฐ๋ฐํ์ ํจ์จ์ ์ธ ํ์ ์ ์ํ Git ๋ธ๋์น ์ ๋ต์ ๊ณ ๋ฏผํ๊ณ ๊ณ์ ๊ฐ์? Git Flow์ GitHub Flow์ ํต์ฌ ๊ฐ๋ ๋ถํฐ ์ฅ๋จ์ , ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ ํ์ ๋ง๋ ์ ๋ต์ ์ ํํ๊ณ ์ด์ํ๋ ์ค์ง์ ์ธ ํ๊น์ง '๋ธ๋ฃจ'๊ฐ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,442๊ฐ16๋ถ
[๐ค] TypeScript ํ์ ๊ฐ๋: ๋ฐํ์ ํ์ ์์ ์ฑ์ ์ํ ํ์ ํจํด ์ ๋ณตํด์
TypeScript์์ ๋ฐํ์์ ๋ณ์์ ํ์ ์ ์์ ํ๊ฒ ์ขํ๋(Narrowing) ๋ฐฉ๋ฒ์ธ ํ์ ๊ฐ๋(Type Guard)์ ๋ํด ์์ธํ ์์๋ด์. `typeof`, `instanceof`, `in` ์ฐ์ฐ์๋ถํฐ ์ฌ์ฉ์ ์ ์ ํ์ ๊ฐ๋๊น์ง, ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ๊ฒฌ๊ณ ํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ตํ๋ด์.
- ๋จ์ด: 2,542๊ฐ28๋ถ
[๐ค] React Query (TanStack Query) ์ฌํ: ๋ฐ์ดํฐ ํ์นญ, ์บ์ฑ, ๋๊ธฐํ ์ ๋ต์ผ๋ก ์น ์ฑ ์ฑ๋ฅ ๊ทน๋ํํด์
React Query (TanStack Query)๋ฅผ ํ์ฉํ์ฌ ๋ณต์กํ ์๋ฒ ์ํ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ์ง๋ฅ์ ์ธ ์บ์ฑ๊ณผ ์๋ ๋๊ธฐํ ์ ๋ต์ผ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ๊ณผ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ทน๋ํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. useQuery, useMutation, useInfiniteQuery ๋ฑ ํต์ฌ ํ ๊ณผ ์ค์ ์ต์ ํ ํ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 1,917๊ฐ22๋ถ
[๐ค] React Suspense์ ErrorBoundary: ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI ๊ฒฝํ์ ์ํ ์ค์ ๊ฐ์ด๋
React ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ํ์ ํ Suspense์ ErrorBoundary์ ๊ฐ๋ ฅํ ์กฐํฉ์ ๊น์ด ์๊ฒ ๋ค๋ค์. ๋ก๋ฉ ์ํ์ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ฐ์ํ๊ฒ ๊ด๋ฆฌํ์ฌ ๋์ฑ ๊ฒฌ๊ณ ํ๊ณ ๋ถ๋๋ฌ์ด UI๋ฅผ ๋ง๋๋ ์ค์ ํ๊ณผ ์ฝ๋ ์์๋ฅผ ํ์ธํด ๋ณด์ธ์.
- ๋จ์ด: 1,302๊ฐ16๋ถ
[๐ค] CSS Container Queries: ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๋ฐ์ํ ๋์์ธ์ ์๋ก์ด ์งํ
๋ฏธ๋์ด ์ฟผ๋ฆฌ์ ํ๊ณ๋ฅผ ๋์ด, ์ปดํฌ๋ํธ ์์ฒด์ ํฌ๊ธฐ์ ๋ฐ๋ผ ์คํ์ผ์ ์กฐ์ ํ๋ CSS Container Queries๋ฅผ ๊น์ด ์๊ฒ ์์๋ณด๊ณ ์ค๋ฌด ์ ์ฉ ๋ฐฉ๋ฒ์ ์๋ดํด ๋๋ ค์.
- ๋จ์ด: 1,681๊ฐ19๋ถ
[๐ค] Next.js 15 ๊ณ ๊ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต: fetch์ revalidate ์ฌ์ธต ๋ถ์
Next.js 15์์ `fetch` API์ ๊ฐ๋ ฅํ ์บ์ฑ ๋ฉ์ปค๋์ฆ๊ณผ `revalidate` ์ต์ ์ ํ์ฉํ์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ธต์ ์ผ๋ก ๋ค๋ฃจ์ด์. ์ค๋ฌด ์์๋ฅผ ํตํด ์๋ฒ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์บ์ฑ ์ ๋ต์ ํจ๊ณผ์ ์ผ๋ก ์ ์ฉํ๋ ํ์ ์ ๊ณตํด์.
๋จ์ด: 1,320๊ฐ14๋ถ[๐ค] Next.js App Router: generateStaticParams๋ก ๋์ ๋ผ์ฐํ ๋น๋ ์ต์ ํํ๊ธฐ
Next.js App Router์์ generateStaticParams ํจ์๋ฅผ ํ์ฉํ์ฌ ๋์ ๋ผ์ฐํ ์ ์ ์ ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ์์ฑํ๊ณ ๋น๋ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์.
๋จ์ด: 1,891๊ฐ22๋ถ[๐ค] React ๋ ๋๋ง ์ต์ ํ: useMemo, useCallback, React.memo ์๋ฒฝ ๊ฐ์ด๋
์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ React ๋ ๋๋ง ์ต์ ํ ๊ฐ์ด๋. useMemo, useCallback, React.memo์ ์ ํํ ์ฌ์ฉ๋ฒ๊ณผ ์ค๋ฌด์์ ํํ ์ ์ง๋ฅด๋ ์ค์, ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฑ๋ฅ ํฅ์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ ค์.
๋จ์ด: 2,145๊ฐ24๋ถ[๐ค] JavaScript Proxy์ Reflect ์ฌ์ธต ๋ถ์: ๋ฉํ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์ฝ๋ ๊ฐํํ๊ธฐ
JavaScript Proxy์ Reflect API๋ฅผ ํ์ฉํ ๋ฉํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ ์ฌ์ธต ๋ถ์ํด์. ๊ฐ์ฒด ์ ๊ทผ ์ ์ด, ์ ํจ์ฑ ๊ฒ์ฌ, ๋ก๊น , ๋ฐ์ํ ์์คํ ๊ตฌํ ๋ฑ ์ค์ฉ์ ์ธ ํ์ฉ ์ฌ๋ก๋ฅผ ํตํด ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
- ๋จ์ด: 2,029๊ฐ24๋ถ
[๐ค] React/Next.js ๋ฒ๋ค ์ต์ ํ: ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ์๋ฒฝ ๊ฐ์ด๋
React์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๊ณ ๋ก๋ฉ ์๋๋ฅผ ๊ฐ์ ํ๋ ์ฝ๋ ์คํ๋ฆฌํ ๊ณผ ๋ ์ด์ง ๋ก๋ฉ ๊ธฐ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์นํฉ ์ค์ ๋ถํฐ React.lazy, Next.js dynamic import๊น์ง ๋ค๋ค์.
- ๋จ์ด: 1,770๊ฐ20๋ถ
[๐ค] React์ `useOptimistic` ํ ์ผ๋ก ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ตฌํํ๊ธฐ: Server Actions์ ํจ๊ป
React 18/19์ `useOptimistic` ํ ์ ํ์ฉํ์ฌ Server Actions์ ์ฐ๋๋๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ฉ์ ์ธ ์์์ ํจ๊ป ์์ธํ ์์๋ด์. ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
๋จ์ด: 1,561๊ฐ17๋ถ[๐ค] TypeScript const Type Parameters: ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ๊ฐํ์ ์ค์ฉ์ ์ธ ํ์ฉ๋ฒ
TypeScript 5.0์ ๋์ ๋ const Type Parameters๋ฅผ ํ์ฉํ์ฌ ์ ๋ค๋ฆญ ํจ์์ ๋ฆฌํฐ๋ด ํ์ ์ถ๋ก ์ ์ ๊ตํ๊ฒ ์ ์ดํ๊ณ , ๋์ฑ ๊ฒฌ๊ณ ํ ํ์ ์์คํ ์ ๊ตฌ์ถํ๋ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. as const์์ ์ฐจ์ด์ ๊ณผ ์ค์ ์ฝ๋ ์์๋ฅผ ํตํด ์ด์ค๊ธ ๊ฐ๋ฐ์๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,028๊ฐ22๋ถ
[๐ค] Next.js/React ์ฑ CLS ์ต์ ํ: ์ํํธ ์๋ ์ฌ์ฉ์ ๊ฒฝํ ๋ง๋ค๊ธฐ
Next.js์ React ์ ํ๋ฆฌ์ผ์ด์ ์์ Cumulative Layout Shift(CLS) ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ์ค์ง์ ์ธ ์ ๋ต๊ณผ ์ฝ๋ ์์๋ฅผ ์์ธํ ์์๋ณด์ธ์. ์น ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ ์์์ธ CLS๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,740๊ฐ21๋ถ
[๐ค] Next.js SSR, SSG, ISR ๋ ๋๋ง ์ ๋ต: App Router์์ ์ต์ ์ ์ ํ์?
Next.js App Router์์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR), ์ ์ ์ฌ์ดํธ ์์ฑ(SSG), ์ฆ๋ถ ์ ์ ์ฌ์์ฑ(ISR) ๊ฐ ๋ ๋๋ง ์ ๋ต์ ๋์ ์๋ฆฌ, ์ฅ๋จ์ , ์ค์ ํ์ฉ ๋ฐ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋น๊ต ๋ถ์ํด๋๋ ค์.
- ๋จ์ด: 1,478๊ฐ17๋ถ
[๐ค] React Context API์ Zustand: ์ ์ญ ์ํ ๊ด๋ฆฌ, ์ธ์ ๋ฌด์์ ์จ์ผ ํ ๊น์?
React ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ณ ๋ฏผํ๊ณ ๊ณ์ ๊ฐ์? Context API์ ๊ฐ๋ฒผ์ด ์ธ๋ถ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Zustand๋ฅผ ๋น๊ต ๋ถ์ํ๊ณ , ์ค๋ฌด์์ ๊ฐ ๋๊ตฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ฉํ๋ ์ ๋ต์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,004๊ฐ24๋ถ
[๐ค] Turborepo๋ก Next.js ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ถ: ํจ์จ์ ์ธ ๊ฐ๋ฐ ๋ฐ ์ต์ ํ ์ ๋ต
Turborepo๋ฅผ ํ์ฉํ์ฌ Next.js ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๊ตฌ์ฑํ๊ณ , ๊ณต์ ์ปดํฌ๋ํธ, ์ ํธ๋ฆฌํฐ, CI/CD ์ต์ ํ ๋ฐฉ์์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์ค๋ช ํด ๋๋ ค์.
- ๋จ์ด: 2,338๊ฐ27๋ถ
[๐ค] React useEffect ํ , ์ด์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์! (์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ์๋ฒฝ ๊ฐ์ด๋)
React ๊ฐ๋ฐ์์ ํ์์ ์ธ useEffect ํ ์ ๋์ ์๋ฆฌ๋ถํฐ ์์กด์ฑ ๋ฐฐ์ด, ํด๋ฆฐ์ ํจ์ ํ์ฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค๋ฌด์์ ์์ฃผ ๊ฒช๋ ์ค์์ ์ต์ ํ ์ ๋ต๊น์ง, ์ด์ค๊ธ ๊ฐ๋ฐ์๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋๋ฅผ ์ ๊ณตํด์.
- ๋จ์ด: 3,284๊ฐ31๋ถ
[๐ค] Next.js Server Actions ์ค์ : ์๋ฌ ์ฒ๋ฆฌ, ์ ํจ์ฑ ๊ฒ์ฌ, ๋๊ด์ UI ์ ๋ฐ์ดํธ
Next.js Server Actions๋ฅผ ์ค๋ฌด์ ์ ์ฉํ ๋ ๋ง์ฃผํ๋ ์๋ฌ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋๊ด์ UI ์ ๋ฐ์ดํธ ๊ธฐ๋ฒ์ ์์ธํ ์ฝ๋ ์์์ ํจ๊ป ์์๋ณด์ธ์.
๋จ์ด: 1,983๊ฐ21๋ถ[๐ค] TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์๋ฒฝ ๊ฐ์ด๋: ์ค์ ํ์ฉ ํจํด
TypeScript ์ ํธ๋ฆฌํฐ ํ์ ์ ํต์ฌ ๊ฐ๋ ๊ณผ ์ค์ ํ์ฉ๋ฒ์ ๊น์ด ์๊ฒ ๋ค๋ค์. Pick, Omit, Partial, Required ๋ฑ ์์ฃผ ์ฐ๋ ์ ํธ๋ฆฌํฐ ํ์ ์ผ๋ก ๋ณต์กํ ํ์ ์ ํจ๊ณผ์ ์ผ๋ก ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์. ํ์ ์คํฌ๋ฆฝํธ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํด์.
- ๋จ์ด: 1,712๊ฐ20๋ถ
[๐ค] Next.js App Router ๋ฏธ๋ค์จ์ด: ๊ฐ๋ ฅํ ์์ฒญ ์ฒ๋ฆฌ ์ ๋ต๊ณผ ์ค์ ์์
Next.js App Router ํ๊ฒฝ์์ ๋ฏธ๋ค์จ์ด๋ฅผ ํ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ, ๋ฆฌ๋ค์ด๋ ์ , ๊ตญ์ ํ ๋ฑ์ ์์ฒญ ์ฒ๋ฆฌ ๋ก์ง์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ค์ ์์ ์ ํจ๊ป ์์ธํ ์์๋ณด์ธ์.
- ๋จ์ด: 1,630๊ฐ19๋ถ
[๐ค] ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ ์ฌํ: ์ค์ฉ์ ์ธ ํจํด๊ณผ ํํ ์คํด๋ค
ํ์ ์คํฌ๋ฆฝํธ ์ ๋ค๋ฆญ(Generics)์ ๊น์ด ์ดํดํ๊ณ , ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉ๋๋ ์ ๋ค๋ฆญ ํจํด๊ณผ ํํ ๊ฒช๋ ์คํด๋ค์ ์ค์ ์ฝ๋ ์์์ ํจ๊ป ์ฝ๊ณ ๋ช ํํ๊ฒ ์ค๋ช ํด ๋๋ ค์. ํ์ ์์ ์ฑ๊ณผ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.
๋จ์ด: 1,860๊ฐ18๋ถ[๐ค] Next.js Route Handler: App Router์์ ์์ ํ๊ณ ํจ์จ์ ์ธ API ๊ตฌ์ถํ๊ธฐ (์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ ํฌํจ)
Next.js App Router์ Route Handler๋ฅผ ์ฌ์ฉํ์ฌ API ์๋ํฌ์ธํธ๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์ธํ ์์๋ด์. ์ธ์ฆ, ์๋ฌ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์บ์ฑ ์ ๋ต์ ํฌํจํ ์ค์ฉ์ ์ธ ํ์ผ๋ก ์์ ํ๊ณ ํจ์จ์ ์ธ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ตํ๋ด์.
- ๋จ์ด: 1,934๊ฐ22๋ถ
[๐ค] Next.js Image ์ปดํฌ๋ํธ ์ต์ ํ: Core Web Vitals ๊ฐ์ ๋ถํฐ ์ค์ ํ์ฉ๊น์ง
Next.js์ Image ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ์ฌ ์น ์ฑ๋ฅ ํต์ฌ ์งํ์ธ Core Web Vitals๋ฅผ ๊ฐ์ ํ๊ณ , ๋ค์ํ ์ต์ ํ ์ต์ ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ธ๋ฃจ๊ฐ ์์ธํ ์๋ ค๋๋ ค์.
- ๋จ์ด: 2,187๊ฐ25๋ถ
[๐ค] Next.js 14.1+์ ํ์ : Partial Prerendering (PPR) ์๋ฒฝ ๊ฐ์ด๋์ ์ค์ ์ต์ ํ ์ ๋ต
Next.js 14.1๋ถํฐ ๋์ ๋ Partial Prerendering (PPR)์ ํตํด ์ด๊ธฐ ๋ก๋ฉ ์๋๋ฅผ ๊ทน๋ํํ๊ณ ๋์ ์ฝํ ์ธ ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ฃจ์ด์. PPR์ ๋์ ์๋ฆฌ๋ถํฐ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ ๋ต๊น์ง, ๊ฐ๋ฐ์๋ค์ด ๊ถ๊ธํดํ๋ ๋ชจ๋ ๊ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,792๊ฐ19๋ถ
[๐ค] TypeScript ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋: ๋ณต์กํ ํ์ ๋ ์์ฝ๊ฒ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ
TypeScript ๊ฐ๋ฐ์์ ๋ง์ฃผํ๋ ๋ณต์กํ ํ์ ์ถ๋ก ๋ฌธ์ , ์กฐ๊ฑด๋ถ ํ์ ๊ณผ infer ํค์๋๋ฅผ ํ์ฉํ๋ฉด ํจ์ฌ ์ฐ์ํ๊ณ ๊ฐ๋ ฅํ๊ฒ ํด๊ฒฐํ ์ ์์ด์. ์ค์ ์์ ์ ํจ๊ป ๊ทธ ํ์ฉ๋ฒ์ ์ฌ๋ ์๊ฒ ๋ค๋ค๋ด ๋๋ค.
- ๋จ์ด: 1,705๊ฐ21๋ถ
[๐ค] JavaScript ์ด๋ฒคํธ ๋ฃจํ(Event Loop) ์์ ์ ๋ณต: ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ฐํ์ ๋์ ์๋ฆฌ
JavaScript์ ํต์ฌ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ธ ์ด๋ฒคํธ ๋ฃจํ์ ๋์ ์๋ฆฌ๋ฅผ ์ฌ๋ ์๊ฒ ํํค์ณ ๋ด์. ์ฝ ์คํ, ํ์คํฌ ํ, ๋ง์ดํฌ๋กํ์คํฌ ํ์์ ์ํธ์์ฉ์ ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ง์ฃผ์น๋ ๋น๋๊ธฐ ์ฝ๋์ ๋์์ ๋ช ํํ ์์ธกํ๋ ๋ฐฉ๋ฒ์ ์๋ ค๋๋ ค์.
- ๋จ์ด: 1,964๊ฐ23๋ถ
[๐ค] Next.js Server & Client Components, ์ค์ ์์ ํ๋ช ํ๊ฒ ์ ํํ๋ ๊ฐ์ด๋
Next.js App Router์์ Server Components์ Client Components ์ค ์ด๋ค ๊ฒ์ ์ฌ์ฉํด์ผ ํ ์ง ๊ณ ๋ฏผ์ด์ ๊ฐ์? ์ด ๊ธ์์ ๋ ์ปดํฌ๋ํธ์ ํต์ฌ ์ฐจ์ด์ , ์ฌ์ฉ ์์ , ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ์ค์ ์ ๋ต์ ๋ธ๋ฃจ๊ฐ ์๋ ค๋๋ฆด๊ฒ์.
- ๋จ์ด: 1,879๊ฐ21๋ถ
[๐ค] TypeScript satisfies ์ฐ์ฐ์: ํ์ ์ถ๋ก ๊ณผ ์์ ์ฑ์ ๋์์ ์ก๋ ๋น๋ฒ
TypeScript์ `satisfies` ์ฐ์ฐ์๋ฅผ ํ์ฉํ์ฌ ํ์ ์ถ๋ก ์ ์ ์ฐ์ฑ์ ์ ์งํ๋ฉด์๋ ์๊ฒฉํ ํ์ ์์ ์ฑ์ ํ๋ณดํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์. ์ค์ฉ์ ์ธ ์์๋ฅผ ํตํด ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ ธํ์ฐ๋ฅผ ๊ณต์ ํฉ๋๋ค.
- ๋จ์ด: 1,211๊ฐ15๋ถ
[๐ค] React 19 ์๋ก์ด ๊ธฐ๋ฅ: use ํ , Actions, ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ๋ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
React 19์ ํต์ฌ ๋ณ๊ฒฝ ์ฌํญ์ธ use ํ , ์๋ฒ ์ก์ , ๊ทธ๋ฆฌ๊ณ React ์ปดํ์ผ๋ฌ์ ๋์ ๋ฐฐ๊ฒฝ๊ณผ ์ค์ ํ์ฉ ์์๋ฅผ ์ด์ค๊ธ ๊ฐ๋ฐ์ ๋๋์ด์ ๋ง์ถฐ ์์ธํ ์ค๋ช ํฉ๋๋ค. ์ต์ React ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ฑ๋ฅ๊ณผ ๊ฐ๋ฐ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐฉ๋ฒ์ ์์๋ณด์ธ์.
- ๋จ์ด: 1,524๊ฐ16๋ถ
[๐ค] Next.js App Router ์บ์ฑ ์ ๋ต: ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ (revalidatePath, revalidateTag) ์๋ฒฝ ๊ฐ์ด๋
Next.js 14 App Router์์ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์บ์ฑ ์ ๋ต๊ณผ revalidatePath, revalidateTag๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ค๋ฌด ์์์ ํจ๊ป ์์ธํ ์์๋ณด๊ณ ์น ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์ธ์.