๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Front-end

Next.js ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์‹œ ๋‹ค๊ตญ์–ด ๋ฌธ๊ตฌ๊ฐ€ ๋น„์–ด ๋ณด์ด๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

Next.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋‹ค๊ตญ์–ด๋ฅผ localStorage ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ดˆ๊ธฐ ํ™”๋ฉด ๋กœ๋”ฉ ์‹œ ๋ฒ„ํŠผ ํ…์ŠคํŠธ๊ฐ€ ๋น„์–ด ์žˆ์Œ
  • ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋Š” ์ •์ƒ์ ์œผ๋กœ ๋‹ค๊ตญ์–ด ๋ฌธ๊ตฌ๊ฐ€ ํ‘œ์‹œ๋จ

์œ„ ๋ฌธ์ œ์˜ ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ ๋‹ค๊ตญ์–ด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ์ˆ˜์ •ํ•˜๊ณ , ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ž‘์—…์„ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.


๋ฌธ์ œ ์›์ธ

const DUMMY_DATA = [
  {
    id: 0,
    name: getLang("WD000266"),
    ...
  },
  ...
];

getLang(code)๋Š” localStorage์—์„œ ๋‹ค๊ตญ์–ด ๋ฌธ์ž์—ด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

export const getLang = (code) => {
  if (typeof window !== "undefined") {
    const langList = JSON.parse(localStorage.getItem("langList"));
    return langList?.[code] || "";
  }
  return code;
};

์ฃผ์š” ์›์ธ

  1. SSR ํ™˜๊ฒฝ์—์„œ window, localStorage๋Š” ์กด์žฌํ•˜์ง€ ์•Š์Œ
    ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง ์ค‘ getLang()์ด ์‹คํ–‰๋˜๋ฉด ๋นˆ ๋ฌธ์ž์—ด ๋ฐ˜ํ™˜
  2. ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€ ์ „์—ญ ์Šค์ฝ”ํ”„์—์„œ ์‹คํ–‰๋จ
    ํด๋ผ์ด์–ธํŠธ์—์„œ localStorage๊ฐ€ ์ค€๋น„๋˜๊ธฐ ์ „ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ๋‹ค๊ตญ์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ•จ

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

1. ํด๋ผ์ด์–ธํŠธ ์ „์šฉ ํ›… ์ƒ์„ฑ

// hooks/useLang.js
import { useEffect, useState } from "react";
import { loadLangList } from "/utils";

const useLang = (code) => {
  const [lang, setLang] = useState("");

  useEffect(() => {
    const langList = loadLangList();
    setLang(langList?.[code] || "");
  }, [code]);

  return lang;
};

export default useLang;
  • ํด๋ผ์ด์–ธํŠธ ๋งˆ์šดํŠธ ์ดํ›„์—๋งŒ localStorage ์ ‘๊ทผ
  • React lifecycle์— ์•ˆ์ „ํ•˜๊ฒŒ ๋Œ€์‘

2. ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ

const Component = () => {
  const nameDownload = useLang("WD000266");
  const namePrint = useLang("WD000258");
  const nameRefresh = useLang("WD000265");
  const nameAction = useLang("WD000264");
  const nameRemove = useLang("WD000227");

  const buttonPanelItems = useMemo(
    () => [
      { id: 0, name: nameDownload, key: "download" },
      { id: 1, name: namePrint, key: "print" },
      { id: 2, name: nameRefresh, key: "refresh" },
      { id: 3, name: nameAction, key: "action" },
      { id: 4, name: nameRemove, key: "remove" },
      { id: 5, name: "bar", key: "bar" },
    ],
    [nameDownload, namePrint, nameRefresh, nameAction, nameRemove]
  );

  return buttonPanelItems.map((item) => (
    <button key={item.key}>{item.name}</button>
  ));
};
  • ์ „์—ญ์ด ์•„๋‹Œ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜
  • useMemo๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ ์ตœ์ ํ™”

3. ์บ์‹œ ์ ์šฉ์œผ๋กœ ์„ฑ๋Šฅ ์ตœ์ ํ™”

// utils/utils.js
let cachedLangList = null;

export const loadLangList = () => {
  if (cachedLangList !== null) return cachedLangList;

  try {
    const stored = localStorage.getItem("langList");
    cachedLangList = stored ? JSON.parse(stored) : {};
  } catch {
    cachedLangList = {};
  }

  return cachedLangList;
};
  • localStorage๋Š” ์ตœ์ดˆ 1ํšŒ๋งŒ ์ ‘๊ทผ
  • ์ดํ›„ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ๋œ ๊ฐ์ฒด ์‚ฌ์šฉ

์„ฑ๋Šฅ ์ธก์ •

useEffect(() => {
  console.time("Original");
  for (let i = 0; i < 1000; i++) {
    getLangOriginal("WD000258");
  }
  console.timeEnd("Original");

  console.time("Optimized");
  for (let i = 0; i < 1000; i++) {
    getLang("WD000258");
  }
  console.timeEnd("Optimized");
}, []);

 

๊ธฐ์กด ๋ฐฉ์‹ ์•ฝ 103.9ms
์ตœ์ ํ™” ๋ฐฉ์‹ ์•ฝ 0.059ms

> ์•ฝ 1,700๋ฐฐ ์„ฑ๋Šฅ ํ–ฅ์ƒ ํ™•์ธ

 

์บ์‹œ ๋™์ž‘ ์›๋ฆฌ

  • cachedLangList๋Š” ๋ชจ๋“ˆ ์Šค์ฝ”ํ”„์— ์ €์žฅ๋˜๋ฉฐ, JavaScript์˜ Heap ๋ฉ”๋ชจ๋ฆฌ์— ์œ ์ง€๋จ
  • SPA ํ™˜๊ฒฝ์—์„œ๋Š” ํŽ˜์ด์ง€ ์ด๋™ ์‹œ์—๋„ ์บ์‹œ ์œ ์ง€
  • ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ์ด๋‚˜ ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ ์‹œ ์บ์‹œ ์ดˆ๊ธฐํ™”

 

์ฃผ์˜์‚ฌํ•ญ

  • ์™ธ๋ถ€์—์„œ localStorage ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋˜๋ฉด ์บ์‹œ ๋ฌดํšจํ™”๊ฐ€ ํ•„์š”ํ•จ
  • ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ์ผ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ์ ์œ ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•จ

 

์ •๋ฆฌ

  ๊ฐœ์„  ์ „ ๊ฐœ์„  ํ›„
SSR ๋Œ€์‘ window, localStorage ์˜ค๋ฅ˜ ๋ฐœ์ƒ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ
์„ฑ๋Šฅ ๋งค๋ฒˆ JSON ํŒŒ์‹ฑ ๋ฐ localStorage ์ ‘๊ทผ ์บ์‹œ ์ ์šฉ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์ ‘๊ทผ
๊ตฌ์กฐ ์ „์—ญ ์Šค์ฝ”ํ”„์—์„œ ์‹คํ–‰ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ๋‹ค๊ตญ์–ด ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ
๋ Œ๋”๋ง ์•ˆ์ •์„ฑ ์ดˆ๊ธฐ ๋นˆ ๋ฌธ์ž์—ด ๋ Œ๋”๋ง ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ ํด๋ผ์ด์–ธํŠธ ๋งˆ์šดํŠธ ํ›„ ์ •ํ™•ํ•˜๊ฒŒ ๋ Œ๋”๋ง ๊ฐ€๋Šฅ

 


 

 

 

 

(๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ๋Œ€์‹  ์ •์  JSON ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์‹œ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ๊ฒ ์ง€๋งŒ,
๋ฐฑ์—”๋“œ์—์„œ๋„ ๋‹ค๊ตญ์–ด ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ ์ ๊ณผ ์ •์  ํŒŒ์ผ ๋ฐฉ์‹์€ ๋ฐฐํฌ ์‹œ์—๋งŒ ๋‹ค๊ตญ์–ด ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ œ์•ฝ์ด ์žˆ์–ด
์ ˆ์ถฉ์•ˆ(CSR)์„ ํ†ตํ•ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.)