ํ”„๋กœ์ ํŠธ

code Splitting, prefetch ๋กœ๋”ฉ์†๋„ ๊ฐœ์„ 

Yuuuki 2024. 7. 10. 11:13

CSR์˜ ๋Œ€ํ‘œ์ ์ธ ์ดˆ๊ธฐ ๋กœ๋”ฉ์†๋„๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ์ž‘์—…๋“ค์„ ์ˆ˜ํ–‰ํ•ด๋ณด์•˜๋‹ค.

 

1. React.lazy์™€ Suspense ์ ์šฉํ•˜๊ธฐ

 

SPA์˜ ๋Œ€ํ‘œ์ ์ธ ๋‹จ์ ์œผ๋กœ, ์ตœ์ดˆ ๋กœ๋“œ์‹œ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ํŒŒ์ผ์„ ๋‹ค์šด๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, ์ดˆ๊ธฐ ๋กœ๋”ฉ์†๋„๊ฐ€ ๊ธธ์–ด์ง„๋‹ค๋Š” ์ ์ด ์žˆ๋‹ค. ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๋Œ€ํ‘œ์ ์ธ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์ค‘ ํ•˜๋‚˜๊ฐ€ lazy Loading์ด๋‹ค.
 
๋™์  load

 

ํ•ด๋‹น ๋ชจ๋“ˆ์ด ํ•„์š”ํ•  ๋•Œ๋งŒ ๋กœ๋“œ → ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…(code splitting)๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ(lazy loading)์„ ๊ฐ€๋Šฅ → ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ํŽ˜์ด์ง€๋‚˜ ๊ธฐ๋Šฅ์— ์ ‘๊ทผํ•  ๋•Œ ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋งŒ ๋‹ค์šด๋กœ๋“œ
๋ฉ๋ƒฅ์ƒํ™œ ์„œ๋น„์Šค์˜ ๋ผ์šฐํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.
const PATHS = {
  main: "/main",
  logIn: "/",
  signUp: "/signup",
  findPassword: "/find/pw",
  profiles: {
    detail: "/profiles/:userId",
    create: "/profiles/create",
    update: "/profiles/update/:userId",
  },
  posts: {
    detail: "/posts/:postId",
    create: "/posts/create",
    update: "/posts/update/:postId",
  },
  search: "/search",
  likes: "/likes/:userId",
  bookmarks: "/bookmarks/:userId",
  notFound: "*",
};
React.lazy์™€ React.Suspense๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์  ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ชฉ์ ์€, ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•  ๋•Œ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™์ ์œผ๋กœ ๋กœ๋“œํ•ด์„œ, ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„์„ ์ตœ์ ํ™”์‹œํ‚ค๋Š” ๊ฒƒ ์ด๋‹ค.
์ฆ‰, ๋กœ๋”ฉ์„ ์ง€์—ฐ์‹œํ‚ค๋ฏ€๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ดˆ๊ธฐ์— ์‚ฌ์šฉํ•  ํ™•๋ฅ ์ด ๋†’์€ ๋ผ์šฐํŠธ๋Š” ์ง€์—ฐ์„ ์‹œํ‚ค์ง€ ์•Š๋Š”๊ฒƒ์ด ์ข‹๋‹ค.
 
๐Ÿ‘‰๐Ÿป logIn, mainํŽ˜์ด์ง€๋Š” ์ œ์™ธ์‹œํ‚ค๊ณ , ์ง€์—ฐ๋กœ๋”ฉ์„ ์ ์šฉํ•ด์ฃผ์—ˆ๋‹ค.
 
BEFORE

 

AFTER
 

 

  • DOMContentLoaded: 111ms → 81ms (27% ๊ฐ์†Œ)
  • Load: 152ms → 102ms (33% ๊ฐ์†Œ)
 

 

2. ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ ์ค„์ด๊ธฐ

 

Vite Visualizer๋กœ Vite์˜ ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์‹œ๊ฐํ•ด๋ณด์•˜๋‹ค.
๊ฐ€์žฅ ํฐ ์˜์—ญ์„ ์ฐจ์ง€ํ•˜๊ณ  ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์€ lodash, firestore, emoji-pikcer ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
 

lodash-es

import { debounce } from "lodash-es";

 

๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ lodash๋Š” CommonJS ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๊ณ , lodash-es๋Š” ES ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, Vite๊ฐ™์€ ๋ฒˆ๋“ค๋Ÿฌ๊ฐ€ Tree Shaking์ด ๊ฐ€๋Šฅํ•ด ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์ฝ”๋“œ๋ฅผ ๋ฒˆ๋“ค์—์„œ ์ œ๊ฑฐํ•˜์—ฌ ๋นŒ๋“œ ๊ฒฝ๋Ÿ‰ํ™”์— ๋„์›€์ด ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

 

 

manualChunks

- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

build์‹œ, ์ด์™€ ๊ฐ™์€๊ฒฝ๊ณ  ๋ฌธ๊ตฌ๊ฐ€ ๋œจ๋Š”๋ฐ, ๋‘๋ฒˆ์งธ ์ง€์‹œ๋กœ๋Š” build.rollupOptions.output.manualChunks ์‚ฌ์šฉํ•˜๋ผ๊ณ  ํ•œ๋‹ค.

 

 

 build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ["react", "react-dom"],
          firebase: [
            "firebase/firestore",
            "firebase/storage",
            "firebase/analytics",
          ],
          ui: [
            "@radix-ui/react-alert-dialog",
            "@radix-ui/react-dialog",
            "@radix-ui/react-dropdown-menu",
            "@radix-ui/react-label",
            "@radix-ui/react-popover",
            "@radix-ui/react-slot",
            "@radix-ui/react-tabs",
          ],
          lodash: ["lodash-es", "lodash-es/debounce"],
          emojiPicker: ["emoji-picker-react"],
        },
      },
    },
  },

 

๐Ÿ‘‰๐Ÿป ํฌ๊ธฐ๊ฐ€ ํฐ firebase, shadui, lodash, emojipicker๋ฅผ ๋ณ„๋„์˜ ์ฒญํฌ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๋นŒ๋“œ ํฌ๊ธฐ๋ฅผ ๋ถ„ํ• ํ•ด์ฃผ์—ˆ๋‹ค.

 

 

 

 

3. Prefetching

Prefetch๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฏธ๋ฆฌ ๋ฆฌ์†Œ์Šค๋“ค์„ fetch๋ฅผ ํ•ด๋‘๊ธฐ ๋•Œ๋ฌธ์—, ๋กœ๋”ฉ์‹œ๊ฐ„์ด ๋‹จ์ถ•๋˜๋ฉด์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

  const handleClickPostCard = async () => {
    await queryClient.prefetchQuery(
      queryKey: [POST, post.id],
      queryFn: () => getPostByPostId(post.id || ""),
    });
    navigate(`/posts/${post.id}`);
  };
์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ๋ฌธํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์€ ํŽ˜์ด์ง€์— ์ ์šฉ์‹œํ‚ค๋Š”๊ฒƒ์ด ํšจ์œจ์ ์ธ๋ฐ, ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค์„ ๋‚ด๋ ค๋ณด๋‹ค๊ฐ€ ๊ด€์‹ฌ ์žˆ๋Š” posrCard๋ฅผ ํด๋ฆญํ•˜๋ฉด postDetailํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค. (์œ ์ € ํ”„๋กœํ•„์ด๋‚˜, search๊ฒฐ๊ณผ๋กœ๋„ ์ ‘๊ทผ๊ฐ€๋Šฅ)
 
์ฃผ๋กœ click ๋˜๋Š” hover, Intersection Observer ํ†ตํ•ด ๋กœ๋“œํ•  ์‹œ์ ์„ ์ •์˜ํ•˜๋Š”๋ฐ, click์„ ์ œ์™ธํ•œ ๊ฒฝ์šฐ๋Š” ๋”์šฑ ๋น ๋ฅธ ๋กœ๋”ฉ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋งŽ์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ•ด ํด๋ฆญ์‹œ prefetchํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.