Published on

๐Ÿ› PROJECT | ๋ธ”๋กœ๊ทธ ์œ ์ง€๋ณด์ˆ˜ ์ผ๊ธฐ #3

Locomote the world ์œ ์ง€๋ณด์ˆ˜ ๊ธฐ๋ก

์ด๋ฒˆ ์—…๋ฐ์ดํŠธ์˜ ์š”์•ฝ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • Nextjs 15๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ
  • Tailwind CSS 4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ
  • TypeScript 5๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ
  • ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ์˜ ์•ฝ๊ฐ„ ๋ณ€๊ฒฝ์ด ์žˆ์—ˆ๋‹ค. (๊ธฐ์กด์—๋Š” gray๋ฅผ neutral๋กœ ๋Œ€์ฒดํ•ด์„œ ์‚ฌ์šฉ).
  • Clean coding & Refactoring
  • Home hero threejs ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ถ”๊ฐ€

Tailwind Nextjs ๋ธ”๋กœ๊ทธ์˜ ์—…๋ฐ์ดํŠธ

์ด ๋ธ”๋กœ๊ทธ๋Š” v2 ๋‚˜์˜ค๊ธฐ ์ „ 2022๋…„์„ ์‹œ์ž‘์œผ๋กœ ์ œ์ž‘ํ•ด์„œ, 2๋…„์ „ ๋ฐ”๋€ v2๋กœ ๋ฐ˜์˜ํ•ด์„œ ์—…๋ฐ์ดํŠธ ํ•˜์ง€ ์•Š์•˜์—ˆ๋‹ค. ์•„๋ž˜๋Š” ๊ณต์‹ ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ ์—…๋ฐ์ดํŠธ ๋กœ๊ทธ ๋งํฌ.

๊ธฐ์กด page router ๋ฐฉ์‹์—์„œ app router ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๊ณ , ๊ทธ์— ๋”ฐ๋ผ ํŽ˜์ด์ง€ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค. ๋˜ํ•œ tailwindcss v4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ๋˜๋ฉด์„œ, ์„ค์ •์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋Š” ์ , ๊ทธ๋ฆฌ๊ณ  ๋ฆฌํŒฉํ† ๋งํ•˜๋ฉด์„œ analytics๋ผ๋˜๊ฐ€ function์ ์ธ ๋ถ€๋ถ„๋“ค์„ ์ „๋ถ€ pliny๋กœ ์ž์ฒด third party๋กœ ๋งŒ๋“ค์–ด์„œ ๋Œ€๋Œ€์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋ชจ์–‘.

์›๋ž˜๋Š” javascript๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ธฐ์กด ๊ฒƒ๊ณผ ๋น„๊ตํ•˜๋ฉด์„œ ์กฐ๊ธˆ์”ฉ migration์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์•Œ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๋กœ ๊ทธ๋ƒฅ v2 ์œ„์—์„œ ๋‹ค์‹œ ์ž‘์—…ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฑฐ์˜ ์ผ์ฃผ์ผ์—์„œ ๋‚ด์ง€๋Š” 10์ผ๋™์•ˆ ๊ฒŒ์œผ๋ฆ„ ํ”ผ์šฐ๋ฉด์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

App router vs Page router

์ž˜ ์„ค๋ช…๋˜์–ด ์žˆ๋Š” ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ฐพ์•„์„œ ์•„๋ž˜ ๋งํฌ๋ฅผ ๋‚จ๊ธด๋‹ค.

๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ์•„๋ฌด๋ฆฌ ์ฐพ์•„๋ด๋„.. ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ์‹์˜ ์„ค๋ช… ๋ฐ–์— ์—†์–ด์„œ ์ผ๋‹จ์€ ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์„ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์€ ๋‚˜์˜์ง€ ์•Š๋‹ค๋Š” ์ƒ๊ฐ์— app router๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค. ๊ฐœ์ธ์ ์œผ๋กœ๋Š” app router๊ฐ€ ๋” ์ง๊ด€์ ์ด๊ฒŒ ๋А๊ปด์กŒ๋‹ค.

custom api๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋”ฐ๋กœ api ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— ๊ด€๋ จ ๊ธฐ๋Šฅ๋“ค์„ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค.
๋ฐ”๋€ v2์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ํ•˜๋Š”๋ฐ, ๊ตฌ์กฐ๋Š” ๋น„์Šทํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด handler๊ฐ€ ์•„๋‹ˆ๋ผ ์•„๋ž˜ ํ…œํ”Œ๋ฆฟ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.

export async function GET(
  _request: NextRequest,
  props: { params: Promise<{ query: string }> },
) {
  try {
    const params = await props.params;

    if (!API_KEY) {
      return NextResponse.json(
        { error: "Missing API configuration" },
        { status: 500 },
      );
    }

    const url = `YOUR_API/?range=/${params.query}/?key=${API_KEY}`;
    const response = await fetch(url);

    if (!response.ok) {
      return NextResponse.json(
        { error: "Failed to fetch data" },
        { status: response.status },
      );
    }

    const data = await response.json();
    return NextResponse.json({ data: [data.values] });
  } catch (error) {
    console.error("Error fetching", error);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 },
    );
  }
}

api๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

const response = await fetch(`/api/example/${encodeURIComponent(query)}`);

Tailwindcss v4

tailwindcss v4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ ๋˜๋ฉด์„œ, ๋” ์ด์ƒ tailwind.config.js ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋‹จ์ˆœ tailwind.css ๋กœ ํ†ต์ผ๋˜์—ˆ๋Š”๋ฐ, ์ด๊ฒŒ ์ƒ๊ฐ๋ณด๋‹ค.. ์—๋Ÿฌ๊ฐ€ ๊ฝค๋‚˜ ๋งŽ์ด ๋‚ฌ์—ˆ๋Š”๋ฐ, ์•„์ง๋„ ์›์ธ์€ ํŒŒ์•…์„ ๋ชปํ–ˆ๋‹ค.

๊ด€๋ จ ์˜ค๋ฅ˜๋Š” ์•„๋ž˜ ๊นƒ ์ด์Šˆ์™€ ๊ฐ™์€ ์ด์Šˆ๋“ค์ด ์—ฌ๋Ÿฌ ์ฐจ๋ก€ ์žˆ์—ˆ๋‹ค.

์•”ํŠผ.. ๊ธฐ์กด tailwind.config.js์žˆ์—ˆ๋˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๋•Œ๋งˆ๋‹ค ํ„ฐ์ ธ์„œ ์ˆœ์„œ ๋งž์ถ”๋ฉด์„œ ํ•ด๊ฒฐํ–ˆ๋˜ ๊ฒƒ์œผ๋กœ ๊ธฐ์–ตํ•˜๋Š”๋ฐ, ์ฐœ์ฐœํ•˜๊ฒŒ ๋งˆ๋ฌด๋ฆฌํ•œ ๊ฒƒ ๊ฐ™์•„์„œ ํ ...

Clean coding & Refactoring

๊ฐœ์ธ์ ์œผ๋กœ pliny๋กœ ๊ธฐ๋Šฅ๋“ค์„ ๋บ€๊ฒƒ์ด ์กฐ๊ธˆ ์•„์‰ฌ์› ๋‹ค. third party๋‹ค ๋ณด๋‹ˆ๊นŒ ์ด๊ฑธ forkingํ•ด์„œ ๊ฐœ์ธ์ด ์ปค์Šคํ…€ํ•˜๋Š” ๋ฐฉ๋ฒ• ๋ฐ–์— ์—†๊ณ , ๋””์ž์ธ์„ ๋„ˆ๋ฌด.. ์™„์„ฑํ•œ ์ƒํƒœ์—์„œ ์˜ฌ๋ฆฌ๋‹ค๋ณด๋‹ˆ ์ปค์Šคํ…€์ด ์ˆ˜์ •์ด ์–ด๋ ต๋‹ค๊ธฐ ๋ณด๋‹จ ๊ท€์ฐฎ์•˜์—ˆ๋‹ค.

UI ๊ธฐ๋Šฅ์„ ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋Šฅ๋“ค ์œ„์ฃผ(ex. comments)๋งŒ ์‚ฌ์šฉํ–ˆ๊ณ , ๊ทธ์™ธ๋Š” ๊ธฐ์กด์— ๋‚ด๊ฐ€ ๋งŒ๋“ค๋˜ ๊ฒƒ๋“ค๋งŒ typescript๋กœ ์˜ฎ๊ฒผ๋‹ค.
๊ธฐ์กด ํšŒ์‚ฌ์—์„œ ๋ฐฐ์šฐ๋˜ refactoring ๋ฐฉ๋ฒ•์„ ๊ฐ€์ ธ์˜ค๋˜, convention์€ ๊ณต์‹ nextjs์— ๋งž๊ฒŒ๋” ํ•˜๋ ค๊ณ  ํ–ˆ์—ˆ๊ณ , typescript์˜ ๋ช…์‹œ์ ์ธ type ์ง€์ •์„ ๋ช‡๋ถ€๋ถ„ ์†Œํ™€ํžˆ ํ•œ๊ฒŒ ์ข€ ๋ฐ˜์„ฑ ํฌ์ธํŠธ..

Wrapup & Conclusion

10์ผ ์—ฌ๊ฐ„ ๋์— ๋‹ค์‹œ locomote the world๊ฐ€ v2๋กœ ์ƒˆ๋กญ๊ฒŒ 2025๋…„์— ๊ฐœํŽธ์ด ๋˜์—ˆ๋‹ค. ๋Œ€๋Œ€์ ์ธ ๋””์ž์ธ ๋ณ€๊ฒฝ์ด๋ผ๊ธฐ ๋ณด๋‹จ ๋ถ€์‚ฐ์Šค๋Ÿฌ์šด ๋ถ€๋ถ„๋“ค์€ ๋Œ€๊ฑฐ ์ถ•์†Œํ–ˆ๋‹ค. ๋„ฃ๊ณ  ์‹ถ์—ˆ๋˜ threejs ๊ธฐ๋Šฅ๋„ home hero์— ์ถ”๊ฐ€๋œ ๊ฑฐ ์™ธ์—” ํฌ๊ฒŒ ์—†์„ ๊ฒƒ ๊ฐ™๋‹ค.
์ผ๋‹จ.. ๋ถˆํ•„์š”ํ–ˆ๋˜ ์ฝ”๋“œ๋“ค์„ ๋Œ€์ฒด๋กœ ๋งŽ์ด ์ •๋ฆฌํ–ˆ๋‹ค๋Š” ๊ฒƒ์„ ์ด๋ฒˆ ๋ฒ„์ „์—์„œ ๋งŒ์กฑํ•˜๋Š” ๊ฒƒ์œผ๋กœ.. ๋‹ค์Œ์—๋Š” ๋” ์ž˜ ๋งŒ๋“ค์–ด๋ด์•ผ๊ฒ ๋‹ค.(ํšŒ์‚ฌ์—์„œ ๋ฐฐ์šด ๊ฒƒ๋“ค ์ž˜ ์‹ธ๋จน์—ˆ๋‹ค..ใ…Žใ…Ž)

๋‹ค์Œ ๋ฒ„์ „์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์ˆ˜์ •๋  ์˜ˆ์ •์ด๋‹ค.

  • ํฌํŠธํด๋ฆฌ์˜ค ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  • ํฌํŠธํด๋ฆฌ์˜ค ์ž์„ธํžˆ ๋ณด๊ธฐ ๋ฐ ํฌ์ŠคํŠธ๋ชจํ…œ ์—ฐ๊ฒฐ
  • ๋ธ”๋กœ๊ทธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
Authors