Next.js๋ React ๊ธฐ๋ฐ SSR์ ๋ํ๋ก ์ธ์ง๋๊ณ ์์ง๋ง ๋ณต์ก์ฑ๊ณผ ๋ฌด๊ฑฐ์ด ๋ฒ๋ค ํฌ๊ธฐ ๋ฑ์ ๋ฌธ์ ์ ๋ ์กด์ฌํฉ๋๋ค.
์ด๋ฌํ ์ํฉ์์ Remix์ Qwik์ ๋ ๋จ์ํ๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
Next.js๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง๋ง, ๊ทธ๋งํผ ๋ณต์กํ API์ ์ค์ ์ด ํ์ํฉ๋๋ค.
๋ํ ํ๋ก์ ํธ๊ฐ ์ปค์ง์๋ก ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์ฆ๊ฐํ๊ณ ํ์ด๋๋ ์ด์
๋น์ฉ์ด ๋์์ง๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ์ทจํ๋ ํ๋ ์์ํฌ๋ค์ด ๋ฑ์ฅํ์ต๋๋ค.
Remix์ Qwik์ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ด๋ฌํ ๋ฌธ์ ์ ์ ๊ทผํฉ๋๋ค.
Remix๋ ์น ํ์ค์ ์ต๋ํ ํ์ฉํ์ฌ ๋จ์ํจ์ ์ถ๊ตฌํ๊ณ , Qwik์ ํ์ด๋๋ ์ด์
๊ณผ์ ์ ๊ทผ๋ณธ์ ์ผ๋ก ์ฌ์ค๊ณํ์ฌ ์ด๊ธฐ ๋ก๋ฉ ์๋๋ฅผ ์ต์ ํํฉ๋๋ค.
Remix๋ React Router์ ์ฐฝ์์๋ค์ด ๋ง๋ ํ๋ ์์ํฌ๋ก, ์น ํ์ค์ ์ต๋ํ ํ์ฉํ๋ ์ฒ ํ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
Next.js๋ ๋ค์ํ ๋ ๋๋ง ์ ๋ต(SSR, SSG, ISR)๊ณผ API ๋ผ์ฐํธ ๋ฑ ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง๋ง,
์ด๋ก ์ธํด API๊ฐ ๋ณต์กํด์ง๊ณ ํ์ต ๊ณก์ ์ด ๋์์ง๋๋ค. ๋ฐ๋ฉด Remix๋ ์น ํ์ค์ ๊ธฐ๋ฐํ ๋จ์ํ ์ ๊ทผ ๋ฐฉ์์ ์ทจํฉ๋๋ค.
Next.js๋ ์์ฒด์ ์ธ ์บ์ฑ ์์คํ ์ ๊ฐ์ง๊ณ ์์ด ๋ณต์กํ ์ค์ ์ด ํ์ํฉ๋๋ค. ๋ฐ๋ฉด Remix๋ HTTP ํ์ค์ธ Cache-Control ํค๋๋ฅผ ํ์ฉํ์ฌ ์ง๊ด์ ์ธ ์บ์ฑ์ ๊ตฌํํฉ๋๋ค.
// Remix์ ๊ฐ๋จํ ์บ์ฑ ์์
export async function loader({ request }) {
const data = await fetchData();
return json(data, {
headers: {
"Cache-Control": "max-age=300, s-maxage=3600"
}
});
}
Next.js๋ ํ์ผ ์์คํ ๊ธฐ๋ฐ ๋ผ์ฐํ ์ ์ฌ์ฉํ๋ฉฐ, API ๋ผ์ฐํธ๋ฅผ ๋ณ๋๋ก ๊ตฌ์ฑํด์ผ ํฉ๋๋ค. Remix๋ ์ค์ฒฉ ๋ผ์ฐํ ์ ์์ฐ์ค๋ฝ๊ฒ ์ง์ํ๋ฉฐ, ๊ฐ ๋ผ์ฐํธ๊ฐ ์์ฒด ๋ก๋์ ์ก์ ์ ๊ฐ์ง๋๋ค.
// Remix์ ์ค์ฒฉ ๋ผ์ฐํ
์์
// routes/dashboard.tsx (๋ถ๋ชจ ๋ผ์ฐํธ)
export default function Dashboard() {
return (
<div>
<h1>๋์๋ณด๋</h1>
<Outlet /> {/* ์์ ๋ผ์ฐํธ๊ฐ ๋ ๋๋ง๋๋ ์์น */}
</div>
);
}
// routes/dashboard.stats.tsx (์์ ๋ผ์ฐํธ)
export default function Stats() {
return <div>ํต๊ณ ๋ฐ์ดํฐ</div>;
}
Next.js๋ getServerSideProps, getStaticProps ๋ฑ ๋ค์ํ ๋ฐ์ดํฐ ํ์นญ ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค. Remix๋ ๋จ์ผํ loader/action ํจํด์ผ๋ก ์ผ๊ด์ฑ ์๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
// Remix์ ๋ฐ์ดํฐ ๋ก๋ฉ ์์
export async function loader({ params }) {
const product = await getProduct(params.id);
return json(product);
}
export default function ProductPage() {
const product = useLoaderData();
return <div>{/* ์ ํ ์ ๋ณด ๋ ๋๋ง */}</div>;
}
Qwik์ Builder.io ํ์ด ๊ฐ๋ฐํ ํ๋ ์์ํฌ๋ก, ํนํ ํ์ด๋๋ ์ด์ ๊ณผ์ ์ ๊ทผ๋ณธ์ ์ผ๋ก ์ฌ์ค๊ณํ์ต๋๋ค.
Next.js์ Remix๋ฅผ ํฌํจํ ๋๋ถ๋ถ์ SSR ํ๋ ์์ํฌ๋ ๋ค์๊ณผ ๊ฐ์ ํ์ด๋๋ ์ด์ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค:
์ด ๊ณผ์ ์ ํนํ ๋ณต์กํ ์ฑ์์ ์๋นํ ์๊ฐ์ด ์์๋๋ฉฐ "ํ์ด๋๋ ์ด์ ๋น์ฉ"์ด๋ผ๊ณ ๋ถ๋ฆฝ๋๋ค. ์ด๋ก ์ธํด TTI(Time to Interactive)๊ฐ ๋ฆ์ด์ง๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ด ์ ํ๋ ์ ์์ต๋๋ค.
Qwik์ ์ด ๋ฌธ์ ๋ฅผ "๋ฆฌ์ค์ด๋ธ(Resumable)" ์ ๊ทผ๋ฒ์ผ๋ก ํด๊ฒฐํฉ๋๋ค. ์ด๋ ์ฑ์ ์ฒ์๋ถํฐ ๋ค์ ์์ํ๋ ๊ฒ์ด ์๋๋ผ, ์๋ฒ์์ ์ค๋จ๋ ์ง์ ๋ถํฐ ์ด์ด์ ์คํํ๋ ๊ฐ๋ ์ ๋๋ค.
// Qwik ์ปดํฌ๋ํธ ์์
export const Counter = component$(() => {
const count = useSignal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick$={() => count.value++}>์ฆ๊ฐ</button>
</div>
);
});
Qwik์ ์ ๊ทผ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋๋ค:
Qwik์ ์ปดํฌ๋ํธ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ๋ถํ ํ์ฌ ํ์ํ ๋๋ง ๋ก๋ํฉ๋๋ค. ์ด๋ฅผ ์ํด ์๋ํ๋ ์ฝ๋ ๋ถํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
// ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ณ๋์ ์ฒญํฌ๋ก ๋ถ๋ฆฌ๋จ
export const MyButton = component$(() => {
return (
<button onClick$={() => {
// ์ด ์ฝ๋๋ ๋ฒํผ์ด ํด๋ฆญ๋ ๋๋ง ๋ก๋๋จ
console.log('๋ฒํผ์ด ํด๋ฆญ๋์์ต๋๋ค');
}}>
ํด๋ฆญ
</button>
);
});
๋ชจ๋ ์ํ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ HTML์ ์ง๋ ฌํํ์ฌ ์ ์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ๋ก๋๋๊ธฐ ์ ์๋ ์ํ ์ ๋ณด๋ฅผ ์ ์งํ ์ ์์ต๋๋ค.
<!-- Qwik์ด ์์ฑํ HTML ์์ -->
<button on:click="./chunks/button_click.js#handler" q:obj="123">ํด๋ฆญ</button>
์ฌ์ฉ์ ์ํธ์์ฉ์ด ์๋ ๋ถ๋ถ๋ง ์ ํ์ ์ผ๋ก ํ์ด๋๋ ์ด์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฒํผ์ ํด๋ฆญํ ๋๋ง ํด๋น ๋ฒํผ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋ก๋๋๊ณ ์คํ๋ฉ๋๋ค.
์ด๋ฌํ ์ ๊ทผ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ ๊ณตํฉ๋๋ค.
Qwik์ ๋ฉํ ํ๋ ์์ํฌ์ธ Qwik City๋ ๋ผ์ฐํ , ๋ฏธ๋ค์จ์ด, ๋ฐ์ดํฐ ๋ก๋ฉ ๋ฑ์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
// Qwik City์ ๋ผ์ฐํธ ์ปดํฌ๋ํธ ์์
export const onGet = async ({ params, response }) => {
const data = await fetchData(params.id);
response.headers.set('Cache-Control', 'max-age=3600');
return { data };
};
export default component$(() => {
const { data } = useEndpoint();
return <div>{/* ๋ฐ์ดํฐ ๋ ๋๋ง */}</div>;
});
Remix๋ ์น ํ์ค์ ํ์ฉํ ๋จ์ํจ์, Qwik์ ํ์ด๋๋ ์ด์
์์ฒด๋ฅผ ์ฌ์ค๊ณํ ์ ๊ทผ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ์ ํ ํ๋ ์์ํฌ๋ฅผ ์ ํํ๋ฉด ๋ฉ๋๋ค.