๐Ÿš€ [Next.js] router.push์˜ ๋™์ž‘ ์›๋ฆฌ์™€ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ (feat. startTransition)


Next router.push์„ ํ†ตํ•ด ๊ฐ™์€ ํŽ˜์ด์ง€ ๋‚ด์—์„œ์˜ ์ด๋™ ์‹œ, ๋กœ๋”ฉ fallback์€ ์™œ ์•ˆ๋ณด์ด๋Š” ๊ฒƒ์ผ๊นŒ?

์ด๋ฒˆ์— ๋‹ค๋ฃจ๊ณ ์ž ํ•˜๋Š” ์ฃผ์ œ๋Š” ์ƒ๊ฐ๋ณด๋‹ค ์—ฌ๋Ÿฌ ์ƒํ™ฉ์ด ์–ฝํ˜€์žˆ์—ˆ๊ณ , ์ƒ๊ฐ๋ณด๋‹ค ๋” ๊นŠ๊ฒŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋œฏ์–ด๋ดค์–ด์•ผ ํ–ˆ๋‹ค.

๋ฌธ์ œ ์ƒํ™ฉ

"next": "13.4.13", // page router ์‚ฌ์šฉ 13.4.6 ์‚ฌ์šฉ ์ค‘ ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ณ  ๋ฒ„์ „ ์˜ฌ๋ ค์„œ ์ง„ํ–‰
"@tanstack/react-query": "^4.29.13",
"react": "18.2.0"

ํ•œ ์ปดํฌ๋„ŒํŠธ(AsyncComponent)๊ฐ€ ์žˆ๊ณ , ์ด ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋Š” react-query์˜ useQuery๋ฅผ suspense ์˜ต์…˜์„ ํ™œ์„ฑํ™” ํ•˜์—ฌ ๊ฐ€์ ธ์˜จ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ, ์ƒ๋‹จ์— ๊ฐ์‹ธ์ง„ Suspense์˜ fallback์ธ โ€˜๋กœ๋”ฉ์ค‘โ€™ ํ…์ŠคํŠธ๊ฐ€ ๋ณด์ธ๋‹ค.

์ด์ œ ์ด ์ปดํฌ๋„ŒํŠธ์˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ page๋ฅผ 1์”ฉ ์ฆ๊ฐ€์‹œํ‚ค๊ณ , useQuery์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํ‚ค์— ์ด page ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ‚ค๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์น˜ํ•œ๋‹ค.

function Test() {
    return (
        <Suspense fallback={<h3>๋กœ๋”ฉ์ค‘</h3>}>
            <AsyncComponent />
        </Suspense>
    );
}

function AsyncComponent() {
    const router = useRouter();
    const page = useQueryParam('page', { parser: Number }) ?? 0;
    const { data } = useQuery(
				[{ page }, 'key'], 
				() => fetchFunc(page), 
				{ suspense: true }
		);

    return (
        <>
            <div>{page}</div>
            <div>{data}</div>
            <Button
                onClick={() => {
                    router.push(
                        `${router.pathname}${QS.create({ page: page + 1 })}`,
                        undefined,
                        { shallow: true },
                    );
                }}
            >
                ๋ฒ„ํŠผ
            </Button>
        </>
    );
}

๐Ÿซ  ๊ธฐ๋Œ€ํ•œ ๋™์ž‘๊ณผ ์‹ค์ œ ๋™์ž‘

๊ทธ๋ ‡๋‹ค๋ฉด, ์œ„์—์„œ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์นญํ•ด์˜ค๋Š” ๋™์•ˆ suspense์˜ fallback์ธ โ€˜๋กœ๋”ฉ์ค‘โ€™์ด ๋ณด์—ฌ์•ผํ•  ๊ฒƒ ๊ฐ™์•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์ด์ „ UI๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋‹ค๊ฐ€ ๊ฐ‘์ž๊ธฐ ํˆญ, ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ์—ˆ๋‹ค.

โ†’ react-query์˜ keepPreviousData ์˜ต์…˜๊ณผ ๊ฐ™์€ ๋™์ž‘

๊ต‰์žฅํžˆ ์ดํ•ดํ•  ์ˆ˜ ์—†๋Š” ๋™์ž‘์ด์—ˆ๊ณ , ์ถ”์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์›์ธ ์ค‘ ํ•˜๋‚˜๋Š” ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” React ์˜ state๋กœ์จ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ React๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง์„ ํ•ด์•ผํ•œ๋‹ค๊ณ  ํŒ๋‹จํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹๊นŒ? ํ•˜๋Š” ์ฒซ ๋ฒˆ์งธ ์˜์‹ฌ์„ ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. (โ†’ ์‚ฌ์‹ค ์ด๊ฒŒ ์›์ธ์€ ์•„๋‹ˆ์—ˆ๋‹ค.)

์‹ค์ œ๋กœ ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ page๋ฅผ state๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค. (โ†’ state๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๋ง๊ณ ์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ router.push๋ฅผ ์“ฐ์ง€ ์•Š์•˜๋‹ค๋Š” ์ ์ด ๋” ์ค‘์š”ํ•˜๊ฒŒ ๋ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.)

์•„๋ž˜ ์ฝ”๋“œ
```jsx
function Test() {
    return (
        <Suspense fallback={<h3>๋กœ๋”ฉ์ค‘</h3>}>
            <AsyncComponent />
        </Suspense>
    );
}

function AsyncComponent() {
    const router = useRouter();
    const [page, setPage] = useState(0);    
		const { data } = useSuspendedQuery(
				[{ page }, 'key'], 
				() => fetchFunc(page), 
				{ suspense: true }
		);

    return (
        <>
            <div>{page}</div>
            <div>{data}</div>
            <Button
                onClick={() => {
                    setPage(page + 1);
                }}
            >
                ๋ฒ„ํŠผ
            </Button>
        </>
    );
}
```

๐Ÿ› ๏ธย ์›์ธ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

์‚ฌ์‹ค ์ด๋Ÿฐ ์ €๋Ÿฐ ์‚ฝ์งˆ์„ ์ข€ ํ–ˆ๋‹ค.

ํŽผ์ณ๋ณด๊ธฐ
**state๋กœ ๊ด€๋ฆฌ๋˜์ง€ ์•Š๋Š” query parameter?**

- ์ด๋Ÿฐ ์ €๋Ÿฐ ์˜์‹ฌ๋“ค์„ ํ•˜๋ฉฐ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋’ค์ ธ๋ณด๋‹ค๊ฐ€, Next.js์˜ router ๊ด€๋ จํ•ด์„œ [๊ฐ™์€ ํŽ˜์ด์ง€ ๋‚ด์—์„œ์˜ ์ด๋™](https://nextjs.org/docs/pages/api-reference/functions/use-router#resetting-state-after-navigation)์ด๋ผ๋Š” ์„น์…˜์—์„œ ํŽ˜์ด์ง€ ์ƒํƒœ์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์ฐพ์•˜๋‹ค.

> the page's stateย **will not**ย be reset by default as React does not unmount unless the parent component has changed.
> 

์ด ๋ฌธ์žฅ์—์„œ `unless the parent component has changed` ์— ํ•œ ๋ˆˆ์ด ํŒ”๋ ค์„œ query parameter๊ฐ€ ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ์—๋Š” React์˜ ๋ฆฌ๋ Œ๋”๋ง์ด ์œ ๋ฐœ๋˜์ง€ ์•Š๋Š”๊ฐ€๋ณด๋‹ค, ํ•˜๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. 

- query parameter์˜ ๋ณ€๊ฒฝ โ†’ useQuery์˜ key ๋ณ€๊ฒฝ โ†’ useQuery ๋ฆฌํŒจ์น˜ โ†’ Suspense์— Promise๊ฐ€ ์žกํ˜€์žˆ๋‹ค๊ฐ€ fullfilled๋˜๋ฉด useQuery์˜ data(์ด๊ฑด state๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๊ฐ’์ด๋‹ˆ)๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉฐ ๊ทธ์ œ์„œ์•ผ ๋ฆฌ๋ Œ๋”๋ง ๋˜์–ด ํ™”๋ฉด์— ๋ฐ˜์˜๋˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.
- ๊ทธ๋ž˜์„œ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์ธ Suspense์— key๋ฅผ `router.asPath`๋กœ ์ฃผ์–ด path(query parameter)๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋งˆ๋‹ค ๋ช…์‹œ์ ์œผ๋กœ ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ์ž„์„ ์•Œ๋ ค์คŒ์œผ๋กœ์จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค.(๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.)

์ด ๊ธ€์„ ์“ฐ๋Š” ์‹œ์ ์—์„œ ๋ณด๋ฉด ์ƒ๋‹นํžˆ ๋น„์•ฝ๋„ ๋งŽ๊ณ  ๋…ผ๋ฆฌ์ ์ด์ง€๋„ ์•Š์€ ์ƒ๊ฐ์ด์—ˆ์œผ๋‚˜, ๋‹น์‹œ์—๋Š” ์ด ๋ง๋„์•ˆ๋˜๋Š” ์ƒํ™ฉ์„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ฆฌ๋œ ํ•˜๋‚˜์˜ ์ƒ๊ฐ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ƒํƒœ๋กœ ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ”๋‹ค.

***์‚ฌ์‹ค,*** query parameter๋„ ์ƒํƒœ๋กœ์จ ๊ด€๋ฆฌ๋˜๋Š”๊ฒŒ ๋งž๋‹ค. ๊ทธ๋Ÿฌ๊ธฐ ๋•Œ๋ฌธ์— ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ณ  ๋ณ€๊ฒฝ๋œ ์ฟผ๋ฆฌ ํ‚ค๋กœ ๋ฐ์ดํ„ฐ๋„ refetching ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

์œ„์˜ ์‚ฝ์งˆ ํ›„ ๋ฉฐ์น ์ด ์ง€๋‚˜ startTransition ์„ ์šฐ์—ฐํžˆ ์ ‘ํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , ์ด๋ฒˆ ๋ฌธ์ œ ํ•ด๊ฒฐ์˜ key๊ฐ€ ๋˜์—ˆ๋‹ค.

๐Ÿง€ย tl;dr

Suspense๋ฅผ ์ง€์›ํ•˜๋Š” ๋ผ์šฐํ„ฐ์˜ ๊ฒฝ์šฐ, React์—์„œ๋Š” ๋„ค๋น„๊ฒŒ์ดํŒ…์„startTransition์œผ๋กœ ๊ฐ์‹ธ์„œ ์ œ๊ณตํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

์‹ค์ œ๋กœ Next์˜ router.push ๋™์ž‘์— ์žˆ์–ด์„œ startTransition์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—(page router ๊ธฐ์ค€์œผ๋กœ ์ด๋™ํ•˜๋Š” ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” react element๋ฅผ ๊ทธ๋ฆด ๋•Œ ์‚ฌ์šฉ, app router์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ๋„ค๋น„๊ฒŒ์ดํŒ… ๋™์ž‘์— ์‚ฌ์šฉ), ๋™์ผํ•œ ํŽ˜์ด์ง€์—์„œ query parameter๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ์—๋Š” ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตณ์ด unmount ์‹œํ‚ค์ง€(Suspense์˜ fallback์„ ๋ณด์—ฌ์ฃผ์ง€) ์•Š๋Š”๋‹ค. ์ด ๋•Œ๋ฌธ์— keepPreviousData ์˜ต์…˜์„ ์ค€ ๊ฒƒ ๊ณผ ๊ฐ™์€ ๋™์ž‘์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ key๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค. (๊ฒฐ๊ตญ ์‚ฝ์งˆ๊ธฐ์—์„œ ์‹œ๋„ํ–ˆ๋˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•๊ณผ ๋™์ผํ•˜๊ธด ํ•˜๋‹ค.)

<Suspense fallback={<h3>๋กœ๋”ฉ์ค‘</h3>} key={router.asPath()}>
	<AsyncComponent />
</Suspense>

startTransition์ด ๋ฌด์—‡์ผ๊นŒ?

starttransition1

startTransition์„ ํ†ตํ•ด ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋™์•ˆ, react๋Š” ์ด๋ฏธ ๊ทธ๋ ค์ง„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ ์ง€์‹œ์ผœ ํ˜„์žฌ ์ œ๊ณตํ•˜๋Š” UI๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. (== Suspense๋ฅผ ์‚ฌ์šฉ์ค‘์ธ ๊ฒฝ์šฐ, ์˜๋„์ ์œผ๋กœ fallback์„ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๋Š”๋‹ค.)

์กฐ๊ธˆ ๋” ๋ฐ‘์œผ๋กœ ๋‚ด๋ ค๋ณด๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” scope๋Š” loading indicators๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๊ณ  ํŠธ๋žœ์ง€์…˜์ด ์ˆ˜ํ–‰๋œ๋‹ค๋Š” ์„ค๋ช…์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค. ๋” ์ž์„ธํ•œ ์‚ฌํ•ญ์€ ๊ณต์‹ ๋ฌธ์„œ์— ์žˆ์œผ๋‹ˆ ๊ผญ ์ •๋…ํ•˜์ž!

starttransition2

useTransition์— ๋Œ€ํ•œ ์„ค๋ช…์—๋Š” ๋ณด๋‹ค ์ž์„ธํ•˜๊ฒŒ ์˜ˆ์ œ๊นŒ์ง€ ๋“ค์–ด์žˆ์œผ๋ฏ€๋กœ ์ด๊ฒƒ๋„ ํ•จ๊ป˜ ๋ณด๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

  • useTransition์—์„œ **Preventing unwanted loading indicators ์„น์…˜์—์„œ Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์— transition์„ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ค ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • useTransition๊ณผ ๋‹ค๋ฅธ ์ ์€ startTransition์€ ํŠธ๋žœ์ง€์…˜์œผ๋กœ ์ธํ•ด pending์ด ๋ฐœ์ƒํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” isPending์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

Next์˜ router.push๋„ ์ด๊ฑธ ์‚ฌ์šฉํ•˜๋Š” ๊ฑด ์•„๋‹๊นŒ?

์‹ค์ œ๋กœ, react ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ๋„ค๋น„๊ฒŒ์ดํŒ…์„ transition์œผ๋กœ ํ‘œ๊ธฐํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

**Building a Suspense-enabled router**

์‹ค์ œ๋กœ ํ™•์ธํ•ด๋ณด๊ธฐ ์œ„ํ•ด, ์ผ๋‹จ next github์— ๋“ค์–ด๊ฐ€ ๋ƒ…๋‹ค startTransition์„ ๊ฒ€์ƒ‰ํ•ด๋ณด์•˜๋‹ค.

๊ฒฐ๊ณผ๊ฐ€ ๊ฝค ๋‚˜์˜จ๋‹ค!

์‹ฌ์ง€์–ด app router์˜ ๊ฒฝ์šฐ ์•„์˜ˆ ๋ช…์‹œ์ ์œผ๋กœ startTransition์œผ๋กœ ๋„ค๋น„๊ฒŒ์ดํŒ…์„ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค๋„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

next-link

Link๋ฅผ ํ†ตํ•ด ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ

next-router

router๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ

๊ทธ์น˜๋งŒ ๋‚˜๋Š” page router๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์œผ๋ฏ€๋กœ ์กฐ๊ธˆ ๋” ๋‚ด๋ ค๋ณด๋‹ค๊ฐ€ react element๋ฅผ ๋ Œ๋”๋ง ํ•  ๋•Œ startTransition์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„๋ƒˆ๋‹ค!

next-starttransition

์ฝ”๋“œ๋งŒ ์ด๋ฆฌ ์ €๋ฆฌ ํƒ์ƒ‰ํ•ด๋ณด๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๊ฑฐ๋Œ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋‹ต์„ ๋ฐ”๋กœ ์ฐพ๊ธฐ ์–ด๋ ค์› ๊ณ , ์‹ค์ œ๋กœ ์ € startTransition์„ ๋บ์„ ๋•Œ ๊ธฐ๋Œ€ํ•˜๋Š”๋Œ€๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด์„œ ๊ท€๋‚ฉ์ ์œผ๋กœ ๋‹ต์„ ์ฐพ๊ธฐ๋กœ ํ–ˆ๋‹ค.



์šฐ์„  ์•„๊นŒ ํ…์ŠคํŠธ๋กœ๋งŒ ์„ค๋ช…ํ–ˆ๋˜ ๋ฌธ์ œ ์ƒํ™ฉ์„ ์˜์ƒ์œผ๋กœ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด์ž.

๐Ÿ‘‰ gif๋ผ ์กฐ๊ธˆ ๋Š๋ ค์š”..! ํ•œ๊ตญ์ธ์ด์ง€๋งŒ ์กฐ๊ธˆ๋งŒ ์—ฌ์œ ๋กญ๊ฒŒ ์‹œ์ฒญ ๋ฐ”๋ž๋‹ˆ๋‹ค.

with-start-transition

๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ(Suspense)์— key๋ฅผ ์ฃผ์—ˆ์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

apply-key-to-suspense

โœ…ย ๊ทธ๋ ‡๋‹ค๋ฉด patch-package๋ฅผ ์‚ฌ์šฉํ•ด ์‹ค์ œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ์„ ๋•Œ๋Š”?


patch-package๋ฅผ ์‚ฌ์šฉํ•ด ํŒจํ‚ค์ง€๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๊ธฐ์„œ ๋‹ค๋ฃจ์ง„ ์•Š๊ฒ ๋‹ค. ๊ตฌ๊ธ€๋งํ•˜๋ฉด ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ ๊ธ€๋“ค์ด ๋งŽ์œผ๋‹ˆ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.

edit-package

์•„๋ž˜๋Š” ์‹ค์ œ๋กœ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ์ด๋‹ค.

diff --git a/node_modules/next/dist/client/index.js b/node_modules/next/dist/client/index.js
index 2def4d8..86fd856 100644
--- a/node_modules/next/dist/client/index.js
+++ b/node_modules/next/dist/client/index.js
@@ -385,10 +385,12 @@ function renderReactElement(domEl, fn) {
         // TODO: Remove shouldHydrate variable when React 18 is stable as it can depend on `reactRoot` existing
         shouldHydrate = false;
     } else {
-        const startTransition = _react.default.startTransition;
-        startTransition(()=>{
-            reactRoot.render(reactEl);
-        });
+        // const startTransition = _react.default.startTransition;
+        // startTransition(()=>{
+        //     reactRoot.render(reactEl);
+        // });
+        console.log('start transition ์ œ๊ฑฐ');
+        reactRoot.render(reactEl);
     }

์ •๋ง๋กœ startTransition์„ ์ œ๊ฑฐํ•˜๋‹ˆ๊นŒ ์ฒ˜์Œ ๊ธฐ๋Œ€ํ–ˆ๋˜ ๊ฒƒ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค!

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ด์•ผ ํ• ๊นŒ?

์‚ฌ์‹ค react ๊ณต์‹ ๋ฌธ์„œ์˜ Suspense ์ชฝ์—๋„ startTransition์ด ๋“ฑ์žฅํ•œ๋‹ค. React๋Š” ์ด๋ฏธ ๋ณด์—ฌ์ง€๊ณ  ์žˆ๋Š” ํ™”๋ฉด์„ ๋ฉˆ์ถ”๊ณ  ๋กœ๋”ฉ fallback์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•ด์น  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋งํ•˜๋ฉฐ, ์ด๋ฅผ ํ•ด์†Œํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์•ˆ์œผ๋กœ startTransition์„ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ๋‹ค.

๋˜ํ•œ Suspense-enabled router(Suspense๋ฅผ ์ง€์›ํ•˜๋Š” ๋ผ์šฐํ„ฐ)๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ startTransition์„ ๋ž˜ํ•‘ํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด๋ฒ„๋ฆฐ๋‹ค. (์ž๊ธฐ๋“ค์ด ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ์‹์ด๋ฏ€๋กœ ๋”ฐ๋ฅด๋ผ๋Š” ๊ฒƒ์ด์ง€!)

๊ทธ๋ฆฌ๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์•ˆ์„ ํ•จ๊ป˜ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ฒฐ๋ก ์ ์œผ๋กœ๋Š” query parameter ๋“ฑ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ React์—๊ฒŒ ์ด๊ฑด ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ์ž„์„ ์•Œ๋ ค์ฃผ์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

**Resetting Suspense boundaries on navigation**

  • ์ด๋Š” key props๋ฅผ ํ†ตํ•ด ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ๋‚˜์˜ ๊ฒฝ์šฐ์—๋Š” ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์ธ Suspense์— key๋ฅผ ์ฃผ์–ด ํ•ด๊ฒฐํ–ˆ๋‹ค.
<Suspense fallback={<Txt>๋กœ๋”ฉ์ค‘</Txt>} key={router.asPath()}>
	<AsyncComponent />
</Suspense>

โ›…๏ธ ์ž์ž˜ํ•œ ์‹œํ–‰ ์ฐฉ์˜ค

์šฐ์—ฐํžˆ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์žˆ๋˜ Next ๋ฒ„์ „์ด 13.4.6์ด์—ˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„  ๊ฐ™์€ ๊ฒฝ๋กœ๋กœ ์ด๋™ํ–ˆ์„ ๋•Œ ์•„์˜ˆ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๊ทธ๋ฆฌ์ง€๋„ ์•Š๋Š” ๊ต‰์žฅํžˆ ์ด์ƒํ•œ ๋™์ž‘์„ ํ–ˆ๋‹ค.

  • 13.3.x ๋ฒ„์ „๋Œ€๋กœ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ 13.4.13~ ์ธ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ๋‹ค์‹œ ์„ค์น˜ํ–ˆ์„ ๋•Œ๋Š” ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ–ˆ๋‹ค.
  • 13.4.6 ๋ฒ„์ „์—์„œ HTTPย deleteย ๋ฉ”์†Œ๋“œ ๊ด€๋ จํ•ด์„œย body์— data๋ฅผ ๋„˜๊ฒจ์ค˜๋„ ํ•ญ์ƒ ๋น„์–ด์žˆ๋Š”ย ์ด์Šˆ๋„ ์žˆ๋‹ค๊ฐ€ ์ดํ›„ ๋ฒ„์ „์—์„œ ์ˆ˜์ •๋˜์—ˆ๋Š”๋ฐ ์•„๋ฌด๋ž˜๋„ ๋งˆ์˜ ๋ฒ„์ „์ธ ๊ฒƒ ๊ฐ™๋‹คโ€ฆ. ใ…‚ใ„ทใ…‚ใ„ท
  • ๋‹ค์‹œ ํ•œ ๋ฒˆ.. ์•„๋ฌด๋ฆฌ ๋Œ€์ค‘์ ์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ๊ณ  ํ•ด๋„ ์ž์ฒด์ ์ธ ๋ฒ„๊ทธ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์ž๊ฐํ–ˆ๋‹ค.

์ŠคํŽ˜์…œ ๋•ก์Šคํˆฌ. goo




# ์นดํ…Œ๊ณ ๋ฆฌ