Vue2 ClientOnly ์ปดํฌ๋ํธ ์ ์๊ธฐ
๐ ๋ชฉ์ฐจ
- ์๋ก
- Vue SSR์ ๊ธฐ๋ณธ์ ์ธ ์ค๋ช
- ์ฝ๋ ์ค๋ช
- ๊ฒฐ๋ก
- ์ฐธ๊ณ ์๋ฃ
๐ ์๋ก
์๋ ํ์ธ์.
์ด๋ฒ์ ๋ผ์ด์ ํ๋ก์ ํธ๊ฐ vue๋ฅผ ์ปค์คํฐ๋ง์ด์งํ์ฌ ssr์ ๊ตฌํํ์ฌ client only์ ์ปดํฌ๋ํธ๊ฐ ์์์ต๋๋ค.
๋ฐ๋ผ์ ํ์์ ๋ฐ๋ผ ์ปดํฌ๋ํธ ์ ์ฒด์ ๋๋๋ง ์์ ์ ๊ฒฐ์ ํ๋ Nuxt์์์ <client-only /> ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ฒ ๋์์ต๋๋ค.
๊ทธ์ ๋ฐ๋ผ ์ด๊ธ์์๋ ์ฌ์ฉ๋ฐฉ๋ฒ ๋ฐ ๊ตฌํ ๋ฐฉ์์ ์ค๋ช ํ๊ณ ์ ํฉ๋๋ค.
๐ก Nuxt์์ client-only ์ปดํฌ๋ํธ๋?
ClientOnly ์ปดํฌ๋ํธ๋ ์์์์๋ค์ client-side์์ ๋๋๋ง ์ํต๋๋ค.
๐ Vue SSR์ ๊ธฐ๋ณธ์ ์ธ ์ค๋ช
์ผ๋จ ํด๋น ์ปดํฌ๋ํธ์ ๋ํด์ ์กฐ๊ธ ๋ ๊น๊ฒ ์ดํด๋ฅผ ํ์๋ฉด ๋จผ์ customized vue ssr๋ถ๋ถ์ ๋จผ์ ์ดํด๋ฅผ ํด์ผํฉ๋๋ค.
๋ณดํต SSR์ผ๋๋ server์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํฉํ์ฌ ๋ง๋ staticํ HTML ํ์ผ์ด ํ๋๊ฐ ๋จ์ด์ง๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Client side์ ์์๋ Client Bundle๋ก ์ด์ javscript code ์ฝ์ (hydrate)์ ํ๊ฒ ๋ฉ๋๋ค.
Server Side → Static HTML → Client Side → HTML์ script ์ฝ๋ ์ฝ์ (hydrate) → ์ดํ ๋ชจ๋ ๋์์ CSR๋ก ๋์
๊ทธ๋์ ์ด๋ค side์ด๋์ ๋ฐ๋ผ ์ปดํฌ๋ํธ์ ์๋ช ์ฃผ๊ธฐ๋ ์ด๋ ๊ฒ ๋ฉ๋๋ค.
Server Side | Client Side | |
beforeCreate | V | |
create | V | |
beforeMount | V | |
mount | V |
๐ ์ฝ๋ ์ค๋ช
์ด์ ํ๋ฒ ์ ์ฒด ์ฝ๋๋ฅผ ์ดํด ๋ณผ๊น์? ์ผ๋จ ์ ์ฒด ์ฝ๋๋ ์ด๋ ๊ฒ ์ง์ฌ์์ต๋๋ค.
export default {
name: 'ClientOnly',
functional: true,
render(h, { parent, slots }) {
const { default: defaultSlots = [] } = slots();
if (parent._isMounted) {
return defaultSlots;
}
parent.$once('hook:mounted', () => {
parent.$forceUpdate();
});
return defaultSlots.length > 0 ? defaultSlots.map(() => h(false)) : h(false);
},
};
๊ทธ๋ผ ํ์ค ํ์ค์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
render(h, { parent, slots }) {
// โญ๏ธ ๋จผ์ slots์ ํด๋นํ๋ ๊ฒ๋ค์ ๋ฐฐ์ด๋ก ์ ์ธํด์ค๋๋ค.
// ex>
// <client-only>
// <Comp1/>
// <Comp2/>
// </client-only>
// defaultSlots = [Comp1, Comp2]
const { default: defaultSlots = [] } = slots();
},
};
render(h, { parent, slots }) {
// โญ๏ธ ์์์ ๋ณธ๊ฒ์ฒ๋ผ mounted๊ฐ ํธ์ถ์ด ๋๋ค๋ฉด,
// ์ด๋ฏธ client side ์ด๊ธฐ ๋๋ฌธ์ ๊ทธ๋ฅ children์ render ํฉ๋๋ค.
if (parent._isMounted) {
return defaultSlots;
}
},
};
render(h, { parent, slots }) {
// โญ๏ธ ๋ง์ฝ ์ if (parent._isMounted)์์ ๊ฑธ๋ฌ์ง์ง ์์๋ค๋ฉด ์ง๊ธ์ server side ์
๋๋ค.
// ๊ทธ๋์ parent๊ฐ mounted๋ ์์ (client-side)์์ ์ ์ฌ๋๋๋ง์ ํด์ค๋๋ค.
parent.$once('hook:mounted', () => {
parent.$forceUpdate();
});
},
render(h, { parent, slots }) {
// โญ๏ธ ์ if (parent._isMounted)์์ ๊ฑธ๋ฌ์ง์ง ์์์ผ๋
// slot์ผ๋ก ๋ค์ด์จ ์์ด๋ค์ render๋ฅผ ๋ง์์ค๋๋ค.
return defaultSlots.length > 0 ? defaultSlots.map(() => h(false)) : h(false);
},
};
์ด๋ ๊ฒ ํด์ฃผ์ด์ server-side์ rendering์ ๋ง์ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ๋ฐฉ๋ฒ์ ๊ฐ๋จํฉ๋๋ค.
<!-- Comp1์ csr -->
<client-only>
<Comp1 />
</client-only>
<!-- Comp2๋ ssr -->
<Comp2 />
๐ ๊ฒฐ๋ก
์ด๋ ๊ฒ ํ๋ฒ nuxt์์์ client-only ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํด๋ณด์์ต๋๋ค. ๋ฌผ๋ก nuxt์์๋ placeholder์ ๊ธฐ๋ฅ๊น์ง ์์ง๋ง ๋ ๋ฒจ์ ํ๋ก๋ํธ ๋ด์์๋ ํด๋น ๊ธฐ๋ฅ์ด ํ์ํ์ง ์์๊ฒ ๊ฐ์ ๋ฐ๋ก ๊ตฌํ์ ํด๋์ง๋ ์์์ต๋๋ค. ๊ทธ๋ผ ์ฌ๋ฐ๊ณ ์ฆ๊ฒ๊ฒ client-only๋ฅผ ์ฌ์ฉํด์ฃผ์๊ธฐ๋ฅผ ๋ฐ๋ผ๋ฉด์ ๊ธ์ ๋ง์นฉ๋๋ค.
๊ทธ๋ผ ๊ธด๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค ! ๐๐ปโ๏ธ