์ด๋ฒ ๊ธ์์๋ ์ฒด์ปค ๋ชฉ๋ก ํ๋ฉด์์ ํํฐ, ๊ฒ์, ํ์ด์ง๋ค์ด์
์ํ๋ฅผ ์ด๋ป๊ฒ ๊ด๋ฆฌํ๋์ง ์ ๋ฆฌํ๊ณ ์ ํ๋ค.
ํนํ Next.js ๊ธฐ๋ฐ ํ๊ฒฝ์์ URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์ค์ฌ์ผ๋ก ์ํ๋ฅผ ํตํฉ ๊ด๋ฆฌํ๊ณ ,
์ด๋ฅผ React Query์ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง(SSR)๊น์ง ์ฐ๊ฒฐํด ์ผ๊ด๋ UX์ ์ด๊ธฐ ์ฑ๋ฅ์ ํ๋ณดํ๋ ์ ๋ต์ ์ค๊ณํ๋ค.
๐ ๋ฌธ์ ์ ์
์ฒ์์๋ ํํฐ์ ๊ฒ์ ์ํ๋ฅผ ์ ์ญ ์ํ๋ก ๊ด๋ฆฌํ๊ฑฐ๋ localStorage์ ์ ์ฅํ๋ ๋ฐฉ์์ ๊ณ ๋ คํ์๋ค.
ํ์ง๋ง ์๋์ ๊ฐ์ ์ค์ง์ ์ธ ํ๊ณ์ ์ด ์กด์ฌํ๋ค.
- ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๋ฉด ์ํ๊ฐ ์ด๊ธฐํ๋์ด ์ฌ์ฉ์ฑ์ด ๋จ์ด์ง
- ํํฐ๋ง๋ ์ํ๋ฅผ URL๋ก ๊ณต์ ํ ์ ์์ด ํ์ ์ ๋ถํธํจ
- ๋ธ๋ผ์ฐ์ ๋ค๋ก๊ฐ๊ธฐ/์์ผ๋ก๊ฐ๊ธฐ ๋์์ด ๊ธฐ๋์ ๋ค๋ฅด๊ฒ ์๋ํจ
- SSR์ ํ์ฉํ ์ด๊ธฐ ๋ ๋๋ง๊ณผ ์ฐ๊ฒฐ๋์ง ์์ ๋ก๋ฉ ์ UX๊ฐ ์ข์ง ์์
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, URL ์ฟผ๋ฆฌ ๊ธฐ๋ฐ์ ์ํ ๊ด๋ฆฌ ๋ฐฉ์์ผ๋ก ์ ํํ๋ค.
โ ํด๊ฒฐ ๋ฐฉํฅ: useSearchParams ๊ธฐ๋ฐ์ URL ์ํ ๋๊ธฐํ
Next.js์ useSearchParams๋ฅผ ํ์ฉํด ํํฐ ์ํ, ๊ฒ์์ด, ํ์ด์ง๋ค์ด์
์ ๋ณด๋ฅผ URL ์ฟผ๋ฆฌ๋ก ์ง์ ๊ด๋ฆฌํ๋๋ก ๊ตฌ์ฑํ๋ค.
์ด ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ ์ ๊ณตํ๋ค.
- ์๋ก๊ณ ์นจ ์์๋ ์ํ๊ฐ ์ ์ง๋จ
- ํํฐ๋ง๋ ํ๋ฉด์ URL๋ก ๊ณต์ ํ ์ ์์
- ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ์ ์๋์ผ๋ก ์ฐ๋๋์ด UX๊ฐ ์์ฐ์ค๋ฌ์
- SSR์์๋ ๋์ผํ ์ฟผ๋ฆฌ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฌ์ ๋ก๋ฉํ ์ ์์ด ์ด๊ธฐ ๋ ๋๋ง ์ฑ๋ฅ์ด ํฅ์๋จ
โ๏ธ ์ฃผ์ ๊ตฌํ ๋ด์ฉ
ํด๋น ์ฝ๋๋ค์ ์์ ์ฝ๋๋ค๋ก ๋ณ๊ฒฝํ์ฌ ์์ฑ๋์์ต๋๋ค.
1. URL ํ๋ผ๋ฏธํฐ ์ ๋ฐ์ดํธ ํจ์
์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ URLSearchParams ๊ฐ์ฒด๋ฅผ ํตํด ์ฒ๋ฆฌํ์ผ๋ฉฐ, ํ์์ ๋ฐ๋ผ set, delete ๋ฑ์ ๋ฉ์๋๋ก ๊ฐ์ ๊ฐฑ์ ํ๋๋ก ๊ตฌํํ๋ค.
const updateQueryParams = (newParams: Record<string, any>) => {
const params = new URLSearchParams(window.location.search);
Object.entries(newParams).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.length > 0 ? params.set(key, value.join(',')) : params.delete(key);
} else {
value ? params.set(key, String(value)) : params.delete(key);
}
});
router.push(`/checker?${params.toString()}`, { scroll: false });
};
2. URL์์ ํํฐ ์ํ๋ฅผ ์ด๊ธฐํํ๋ ๋ก์ง
ํ์ด์ง ์ง์ ์ searchParams์์ ๊ฐ ํํฐ ๊ฐ์ ์ฝ์ด๋ค์ฌ ํด๋ผ์ด์ธํธ ์ํ๋ฅผ ๊ตฌ์ฑํ๋ค.
const filterState = useMemo(() => ({
status: searchParams.get('status')?.split(',') || [],
search: searchParams.get('search') || '',
reference_ids: searchParams.get('reference_ids')?.split(',').map(Number) || [],
}), [searchParams]);
3. ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง๊ณผ์ ํตํฉ
์ด๊ธฐ ๋ฐ์ดํฐ ๋ก๋ฉ ์ getServerSideProps๋ฅผ ํ์ฉํ์ฌ URL ์ฟผ๋ฆฌ๋ก๋ถํฐ ํํฐ์ ํ์ด์ง๋ค์ด์
์ ๋ณด๋ฅผ ์ถ์ถํ๊ณ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์๋ค.
export const getServerSideProps = async (context) => {
const query = context.query;
const filter = {
status: query.status?.split(',') || [],
search: query.search || '',
};
const pagination = {
page: Number(query.page) || 1,
page_count: Number(query.page_count) || 50,
};
const data = await getCheckers(pagination, filter);
return { props: { data } };
};
4. React Query์์ ํตํฉ
React Query์์๋ queryKey์ ํํฐ์ ํ์ด์ง ์ ๋ณด๋ฅผ ํจ๊ป ํฌํจ์์ผ, URL์ด ๋ฐ๋๋ฉด ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ refetchํ๋๋ก ๊ตฌ์ฑํ๋ค.
const { data } = useQuery({
queryKey: ['checkers', filterState, pagination],
queryFn: () => getCheckers(pagination, filterState),
initialData: props.data,
});
๐ฏ ๊ฒฐ๊ณผ
์ด์ฒ๋ผ ๊ฐ ์ํ(ํํฐ, ๊ฒ์, ํ์ด์ง๋ค์ด์ )๋ฅผ URL๊ณผ ์ง์ ์ฐ๊ฒฐํจ์ผ๋ก์จ ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์๋ค.
- ํ์ด์ง ์๋ก๊ณ ์นจ ์์๋ ์ฌ์ฉ์๊ฐ ์ค์ ํ ํํฐ ์ํ๊ฐ ์ ์ง๋์๋ค.
- ํํฐ๋ง๋ ๋ชฉ๋ก์ ๊ทธ๋๋ก URL๋ก ๊ณต์ ํ๊ฑฐ๋ ์ ์ฅํ ์ ์๊ฒ ๋์๋ค.
- ๋ธ๋ผ์ฐ์ ๋ค๋ก๊ฐ๊ธฐ/์์ผ๋ก๊ฐ๊ธฐ ๋์์ด ๊ธฐ๋ํ ๋๋ก ์๋ํด UX๊ฐ ์์ฐ์ค๋ฌ์์ก๋ค.
- SSR๊ณผ React Query ์ด๊ธฐ ๋ฐ์ดํฐ ์ธํ ์ด ํตํฉ๋์ด, ์ฒซ ํ์ด์ง ์ง์ ์๋๋ ๋นจ๋ผ์ก๋ค.
๐ ํ๊ณ ๋ฐ ๊ฐ์ ์ฌ์ง
์ด๋ฒ ๊ตฌ์กฐ ์ค๊ณ๋ฅผ ํตํด ํด๋ผ์ด์ธํธ-์๋ฒ ๊ฐ ์ํ ์ผ๊ด์ฑ๊ณผ ๊ณต์ ๊ฐ๋ฅ์ฑ์ด๋ผ๋ ๋ ๋ง๋ฆฌ ํ ๋ผ๋ฅผ ๋ชจ๋ ์ก์ ์ ์์๋ค.
๋ค๋ง ํฅํ ํํฐ ํญ๋ชฉ์ด ๋ง์์ง ๊ฒฝ์ฐ, ์ฟผ๋ฆฌ ๋ฌธ์์ด์ด ๊ธธ์ด์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ด ํ๋ผ๋ฏธํฐ ์์ถ ๋ฑ์ ๊ณ ๋ คํ ๊ณํ์ด๋ค.
๋๊ธ