SSR 진짜 CSR보다 빠를까 ?

Lee·2023년 1월 11일
13

블로그 이전했습니다 ! 블로그 링크

개요

SSR 관해서 회사 면접을 준비하며 이론을 살짝 검색해서 암기하고 있었습니다. 하지만 실재로 사용하본적이 없고 첫페이지 로딩에 대해서 CSR에 비해 빠르다..!, 검색엔진최적화가 잘되어 있다..! 라고 알고만 있었지 실재로 확인을 해본적이 없어 이번에 진짜 빠른게 궁금해서 테스트를 해보려고 합니다.

간단한 CSR, SSR 설명

CSR

CSR은 서버로부터 빈 html파일과 js파일을 받아서 js파일을 이용해 html을 클라이언트에서 처리해 채우는 방식을 의미합니다. 이 동작은 첫 화면에서 유저가 흰화면을 오래 볼 가능성이 있으며 빈 html을 받기 때문에 SEO가 좋지 않습니다.

SSR

SSR은 서버로부터 완성된 html을 받으며 동작과 관련된 js파일을 따로 받아 브라우저가 js파일을 읽는 동안 유저는 빈 화면이 아닌 채워져 있는 html을 보고 있어 빈 화면을 보는 시간이 줄어듭니다. 또한 채워져 있는 html을 받기 때문에 SEO도 좋습니다.

각각 언제 사용할까요 ?

SSR

SSR이 빠르다..! 라고는 하지만 항상 SSR만 사용할수는 없습니다. SSR은 첫 페이지를 로딩할때 사용됩니다.

CSR

모든 페이지에 SSR을 적용하는 것은 오히려 느립니다. 첫 페이지에서 다른 페이지로 이동할때는 CSR은 이미 첫 페이지가 로딩할때 나머지 부분을 구성하는 요소들을 가져왔기 때문에 빠르지만 SSR은 첫 페이지가 로딩한 과정을 다시 실행시켜 더 느립니다.

테스트

우선 mock api를 이용해서 유저 데이터와 기사 데이터를 가져오는 것을 통해 사이트의 속도를 비교하려고 합니다. 성능 측정은 크롬 시크릿 창에서 ligthhouse와 성능 텝에서 측정을 해보겠습니다.

CSR

코드

//index.tsx
import { Articles } from "../component/Articles";
import { Users } from "../component/Users";

export interface Article {
  createdAt: Date;
  title: string;
  id: string;
  url: string;
}
export interface User {
  avatar: string;
  createdAt: Date;
  id: string;
  name: string;
}

const Home = () => {
  return (
    <div>
      <h1>CSR</h1>
      <Users />
      <Articles />
    </div>
  );
};

export default Home;
//User.tsx
import axios from "axios";
import Image from "next/image";
import { useEffect, useState } from "react";
import { User } from "../pages";

export const Users = () => {
  const [users, setUsers] = useState<User[]>();
  useEffect(() => {
    const getUsers = async () => {
      const result = await axios.get(
        "https://63a6a469f8f3f6d4ab0f5e08.mockapi.io/api/users"
      );
      setUsers(result.data);
    };
    getUsers();
  }, []);
  return (
    <>
      <h2>User List</h2>
      <ul>
        {users?.map((item) => (
          <div key={item.id}
          >
            <li>
              <Image height={40} width={40} src={item.avatar} alt={`${item.name} img`} />
              <span>{item.name}</span>
            </li>
          </div>
        ))}
      </ul>
    </>
  );
};
//Articles.tsx
import axios from "axios";
import { useEffect, useState } from "react";
import { Article } from "../pages";

interface ArticlesProps {
  articles: Article[];
}

export const Articles = () => {
  const [articles, setArticles] = useState<Article[]>();
  useEffect(() => {
    const getArticles = async () => {
      const result = await axios.get(
        "https://63a6a469f8f3f6d4ab0f5e08.mockapi.io/api/article"
      );
      setArticles(result.data);
    };
    getArticles();
  }, []);

  return (
    <>
      <h2>Articles List</h2>
      {articles?.map((item) => (
        <li key={item.id}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </>
  );
};

이런식으로 user api를 호출해서 useEffect안에서 setState를 해주는 동작을 한뒤 list를 화면에 보여줍니다.

성능 측정

lighthouse에서 FCP는 0.8초 LCP는 0.8초가 나왔습니다.

SSR

코드

//index.tsx
import axios from "axios";
import { Articles } from "../component/Articles";
import { Users } from "../component/Users";

export interface Article {
  createdAt: Date;
  title: string;
  id: string;
  url: string;
}
export interface User {
  avatar: string;
  createdAt: Date;
  id: string;
  name: string;
}

interface HomeProps {
  users: User[];
  articles: Article[];
}

const Home = ({ users, articles }: HomeProps) => {
  return (
    <div>
      <h1>SSR</h1>
      <Users users={users} />
      <Articles articles={articles} />
    </div>
  );
};

export async function getServerSideProps() {
  const userResult = await axios.get(
    "https://63a6a469f8f3f6d4ab0f5e08.mockapi.io/api/users"
  );
  const articleResult = await axios.get(
    "https://63a6a469f8f3f6d4ab0f5e08.mockapi.io/api/article"
  );

  return { props: { users: userResult.data, articles: articleResult.data } };
}

export default Home;

Users컴포넌트와 Articles컴포넌트는 props으로 데이터를 받을뿐이지 똑같이 map
을 이용해서 리스트를 보여줌으로 생략하겠습니다.

성능측정


lighthouse에서 FCP는 0.8초 LCP는 0.8초가 나왔습니다.

이상하게 나온 결과

SSR, CSR모두 FPC와 LCP가 0.8초로 동일하게 나왔습니다.
성능텝을 보면 이상함이 있는데요

바로 LCP가 text(h1 tag)를 의미하고 있었습니다. 그러므로 CSR에서 FCP와 LCP가 동일하게 나왔습니다. LCP를 image로 변경하고 측정해보겠습니다.

import Image from "next/image";
import { User } from "../pages";

interface Users {
  users: User[];
}

export const Users = ({ users }: Users) => {
  return (
    <>
      <h2>User List</h2>
      <ul>
        {users?.map((item) => (
          <li key={item.id}>
            <div
              style={{ position: "relative", width: "80vw", height: "80vh" }}
            >
              <Image fill src={item.avatar} alt={`${item.name} img`} />
            </div>
            <span>{item.name}</span>
          </li>
        ))}
      </ul>
    </>
  );
};

이런식으로 이미지의 크기를 크게해서 다시 측정해보겠습니다.

이미지 크기를 바꾼 후 성능 측정

CSR


FCP 0.8초, LCP 2.3초 또한 LCP가 이제는 image로 잡혀서 나옵니다.

SSR

FCP 0.9초, LCP 1.5초

LCP를 이미지로 인식하게 바뀌었더니 LCP가 0.8초 단축되었습니다.

기술 자체도 중요한데 어떻게 성능을 측정해야 기술들의 유의미한 차이를 알 수 있는지에 대한 중요성을 생각해보는 시간이였습니다. 혹시 잘못된 부분이나 개선해야할 부분이 보이면 알려주시면 감사하겠습니다 !

profile
프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2023년 1월 11일

궁금해서 댓글 달아봅니다. CSR과 SSR 차이 때문에 발생하는 것이 맞을까요?
보기에는 next/image 사용으로 인해 이미지가 webp 형식으로 변환되어 빠르게 렌더링되는게 아닌가 해서요 :)

1개의 답글