728x90

https://ko.vite.dev/guide/

 

Vite

Vite, 프런트엔드 개발의 새로운 기준

ko.vite.dev

 

Vite는 

성능에 집중한 번들러로

 

개발 환경에서 코드를 변경할 때 parcel, webapack에 비해 빠른 속도를 자랑한다. 

 

이유는 vite의 코드 갱신 방식이 다른 번들러들과 다르기 때문인데, 다른 번들러들은 코드의 업데이트가 있을 때 전체 코드를 다시 번들링 하지만, vite는 브라우저의 요청에 따라 변경된 모듈만 전달하는 방식으로 화면을 갱신하기 때문에,

프로젝트의 코드 크기가 커질수록 갱신 속도가 느려지는 다른 번들러들에 비해, vite는 빠른 속도를 유지할 수 있다는 것이다. ~.~ 

 

또한 배포시에도 ESbuild(트랜스파일러), Rollup(트리셰이킹, 불필요한 코드를 쳐내고 번들로 합침)을 기반으로 동작하기때문에, 웹팩과 같이 증분 빌드를 지원하지는 않지만 빌드 속도 또한 매우 빠르다.

 

 

 

 

리액트 typescript 프로젝트를 vite 기반으로 생성하는 npm 명령어 

npm create vite@latest my-project-name --template react-ts

 

일할때는 webpack만을 사용해봤는데 개인적으로 공부할때 vite를 써보니 확실히 기본 설정도 간단하고 로드 속도도 빨라서 편하다고 느껴졌다. 

728x90

나는 주로 업무를 할 때 styled-component와 emotion을 사용해 컴포넌트들을 관리해왔다. 

props를 넘겨줄 수 있어서 동적인 스타일을 적용하기 편리하고, 러닝커브가 낮아서 쉽게 접할 수 있었기 때문이다. 

 

그러나 최근들어 tailwind css를 쓰는 기업들이 많아지면서, 과연 어떤 부분이 css in js 보다 더 좋은지, 혹은 단점은 없는지 알고싶어졌다. ~,~ 

 

 

tailwind 사용방법은 공식 문서를 참고하자 

 

https://tailwindcss.com/docs/installation

 

Installation - Tailwind CSS

The simplest and fastest way to get up and running with Tailwind CSS from scratch is with the Tailwind CLI tool.

tailwindcss.com

 

 

아래에 내가 알게된 둘의 장단점과 차이에 대해 정리해보겠다. 

 

  • CSS in JS 
    • javascript 파일 내에서 스타일을 정의해서 사용한다. 대표적으로 Styled-component, Emotion이 있다. 
    • styled의 경우 const MyComponent = styled.div`...`; 태그를 변수 형태로 정의해서 사용한다.
    • emotion의 경우 const MyStyle = css``;  => <div css={MyStyle}/> css를 변수로 정의해서 css라는 속성으로 넣어준다.
    • props,state에 따라 간단하게 동적인 스타일을 적용할 수 있다. 
    • 러닝커브가 낮아서 쉽게 사용할 수 있다. 
    • 랜더링 시 javascript 파일 안에 style 태그로 들어간다. 
    • js 코드가 업데이트될때마다 함깨 렌더링 되기 때문에 랜더링 속도가 더 오래걸릴 수 있다. (일부 스타일은 브라우저에서 캐싱되기도 함)

 

 

  • Tailwind CSS
    • 랜더링 시 단일 css파일로 동작해 js 코드의 리랜더링 등에 영항을 받지 않는다. html에서 직접 css를 참조. 
    • 정적 css파일로 동작하기때문에 동적인 스타일을 적용하기위해서는 여러 클래스를 조건에따라 관리해야하는 불편함이 있다. (불가능한것은 아님! classnames 로 조건에 따른 복수의 클래스네임을 관리할 수 있다.)
    • 클래스기반의 캐싱을 사용 class name 안에 <a href="#" class="text-blue-500">+ 198 others</a> 기본적으로 제공하는 스타일을 넣거나,
    • 이렇게 커스텀 스타일을 정의해서 사용하는 것도 가능하다.  
module.exports = {
  theme: {
    colors: {
      'blue': '#1fb6ff',
      'pink': '#ff49db',
      'orange': '#ff7849',
    },
  }
}
------------------------------------------------
<div class="bg-blue text-orange p-4">
  custom text
</div>

   

 

우선 기본적으로 tailwind에서 제공하는 유틸리티 클래스 구조와 rem 단위에 익숙해질 필요가 있겠군... 

 

e.g ) className-"p-4" 는 위아래좌우에 1rem(보통 16px)의 패딩을 넣는다는 뜻,

        className="py-2.5 px-3"은 위아래 10px, 좌우 12px의 패딩을 넣는다는 뜻. 

 

       (1: 0.25rem  ==  4px)

728x90

 
 
Next.js는 서버사이드랜더링과 정적 페이지 생성을 제공하는 React프레임워크이다 
리엑트를 기반으로 동작하기 때문에 리엑트에서 사용되는 기본적인 상태관리나 스타일 관련 라이브러리, 코드 번들러, babel 등을 동일하게 사용할 수 있어 리액트 개발자들이 쉽게 사용할 수 있다. 
 
Next에 대해 알기 전에는(어깨너머로 알 때는)
서버사이드랜더링을 써야하는 이유에 대해 이해하지 못했다.
어차피 유저 인터렉션 많은 페이지에서는 큰 의미가 없는게 아닌가 라고 생각했기 때문이다.

그러나 실제로 사용해보면서,  단순히 서버에서 페이지를 렌더링하는 것 이상의 다양한 성능 최적화와 기본 설정이 제공된다는 사실을 알게 되었다.

서버-클라이언트 구조가 사용자 경험에 긍정적인 영향을  미치는지를 경험하면서, 왜 많은 회사에서 Next를 사용하게 되었는지 이해했다.


내가 알게된 내용에 대해 요약하고, 코드에 적용한 것을 아래에 정리해봤다.



 
1.서버사이드 랜더링과 정적 페이지 생성
데이터가 실시간으로 업데이트 되지 않는 경우, 매번 계속 페이지를 생성하는 것은 비효율적이다. 이럴때
 

  • ISR 을 사용하면 데이터가 갱신될 때만 페이지가 새로 생성하거나, 
  • SSG를 통해 한 번 생성된 페이지를 정적으로 제공해서 서비스의 성능을 높일 수 있다. 
  • 또한 SSR이 기본적으로 제공되기 때문에 서버에서 미리 html을 랜더링해와 초기 로딩속도를 줄일 수 있다. -> SEO최적화에 도움됨. 


** ISR과 SSG의 차이**

- ISR (incremental static regeneration):특정 주기(revalidate)로 페이지를 갱신함. 특정 주기마다 데이터가 업데이트 되는 경우 적합하다. e.g) 뉴스 등 

- SSG (static site generation) : 빌드 타임에 모든 페이지를 정적으로 미리 생성한다. 페이지가 빌드될 때 한 번 생성하고, 그 이후에는 동일한 정적 파일을 계속 제공한다. 데이터를 업데이트하기위해서는 사이트 전체를 다시 빌드해야한다.
자주 변하지 않는 서비스에 적합.  e.g)블로그나 기술 문서 등 
 
 
2. 파일 기반 라우팅
Next.js의 파일 기반 라우팅(App Routing)은 파일 경로만 설정하면 자동으로 URL이 생성되기 때문에 기존 리액트에 비해 프로젝트의 구조가 훨씬 간결해진다. 폴더명이 url이 되는 형식.
 
e.g) App/News/Page.js 의 구조일 때 myappUrl/News 로 접속하면 news 폴더 안의 pages.js 내용이 랜더링 된다. 
 
 
3. 성능 최적화
Next.js는 기본적으로 코드 스플리팅이미지 최적화를 제공하기 때문에 로딩 속도 최적화가 가능하다. 단순히 기능을 제공할 뿐만 아니라, 유저 사용성을 높이기 위해서는 next를 쓰는 것이 좋다. 물론 기본 리엑트에서도 스플리팅과 이미지 최적화 모두 가능하지만 설정이 필요하다. 
 
 
 


스팀에서 제공하는 api를 사용해서 간단한 서버사이드 랜더링 페이지를 구현 해보자 !
 
 
파일 구조(root)
 

App 폴더

ㄴ page.tsx

Component 폴더

ㄴ gamelist.tsx

ㄴ newslist.tsx

API 폴더

ㄴ api.tsx

 
 
 
page.tsx  : 서버 사이드 렌더링을 통해 Steam API 데이터를 가져오는 메인 페이지  
현재는 내 게임정보와 스팀 뉴스를 가져오고 있다.

  • 서버 컴포넌트에서 가져온 데이터를 클라이언트 컴포넌트로 넘겨줌 으로서 빠른 초기 로딩, SEO 최적화라는 장점이 있다. 
import { fetchMyGames, fetchSteamNews } from "@/api/getSteamApi";
import GameList from "@/components/GameList";
import SteamNewsList from "@/components/NewsItem";

export default async function HomePage() {
  const news = await fetchSteamNews();
  if (!news || news.length === 0) {
    return <p>No news available</p>;
  }

  const myGame = await fetchMyGames();

  return (
    <div>
      <h3>Steam News</h3>
      <SteamNewsList news={news} /> 
      <h3>You Played</h3>
      <GameList games={myGame} />
    </div>
  );
}

 
 
 
api.tsx : 스팀에서 내 개임 정보를 가져오는 api .
api와 id는 .env.local에서 환경변수 정의 후 가져오면 된다. 

export async function fetchMyGames() {
  const apiKey = process.env.API_KEY;
  const steamId = process.env.MY_STEAM_ID;
  const url = `url 생략...`;
  try {
    const res = await fetch(url);

    if (!res.ok) {
      throw new Error(
        `Mygame API url을 가져올 수 없습니다: code ${res.status}`
      );
    }

    const data = await res.json();

    const games: Game[] = data.response.games;

    if (games && games.length > 0) {
      console.log(`total ${games.length} games:`);
    } else {
      console.log("total 0 games");
    }

    return games; // 게임 목록 반환
  } catch (error) {
    console.error("[error] Fetch game info failed:", error);
    return [];
  }
}

 
gamelist.tsx : 스팀 아이디를 통해 플레이한 게임의 정보를 가져오는 코드 
 

  • 서버 컴포넌트에서 데이터를 가져오고, 클라이언트 컴포넌트에서 UI를 렌더링하는 방식으로 서버와 클라이언트가 분리됨. 서버 부하를 줄이고 성능을 최적화 할 수 있다. 
import React from "react";
import { Game } from "./type/type";
function GameIcon({
  appid,
  img_icon_url,
}: {
  appid: number;
  img_icon_url: string;
}) {
  const imageUrl = `https://media.steampowered.com/steamcommunity/public/images/apps/${appid}/${img_icon_url}.jpg`;

  return (
    <img
      src={imageUrl}
      alt="Game Icon"
      width="50"
      height="50"
      style={{ borderRadius: 50 }}
    />
  );
}

export default function GameList({ games }: { games: Game[] }) {
  return (
    <ul>
      {games.map((game) => (
        <li
          key={game.appid}
          style={{ listStyle: "none", marginBottom: "10px" }}
        >
          <h4>{game.name}</h4>
          <GameIcon appid={game.appid} img_icon_url={game.img_icon_url} />
          <p>Playtime: {Math.floor(game.playtime_forever / 60)} hours</p>
        </li>
      ))}
    </ul>
  );
}

 
 
 
 
 
지금까지 서버 -클라이언트 컴포넌트를 분리하여 최적화된 페이지를 만드는 법을 정리해보았다.
 
현재 코드는 페이지가 하나로 파일 기반 라우팅이 적용되지 않았다. 
코드가 업데이트되는대로 해당 부분을 업로드하겠다.
 
더불어 뉴스 컴포넌트나 스팀 api 사용 관련 설명 또한 추후에 첨부하겠음! 
 

728x90

+ Recent posts