오픈소스 코드 분석

오.코.분: vercel/next-lean SEO 폴더

sakuraop 2023. 11. 15. 20:52

오.코.분 목표: 트렌드를 만들어 내는 Vercel의 코드를 (쉬운것부터) 분석하고 내 코드 스타일을 개선하자.

새로 학습한 내용

  • Next.js Head 태그로 SEO 성능 높이기
  • onChange 이벤트 내에서 라이브러리 dynamic import
  • Modal은 dynamic import로 코드 스플리팅하여 초기 렌더링 속도 향상
  • SyntaxHighlighter와 react-modal 이라는 라이브러리를 써 봐야겠다.
  • Next.js 13 이전 버전에서는 getServerSideProps로 데이터를 페칭한다.

오픈 소스 코드 분석 대상

vercel/next-learn 레포지토리의 SEO 폴더

https://github.com/vercel/next-learn/tree/main/seo

1. Next.js는 페이지별로 Head 태그를 통해 SEO 성능을 높일 수 있다.

// index.js

  <Head>
    <title>Core Web Vitals</title>
    <meta name="description" content="Core web vitals walk through" />
    <link rel="icon" href="/favicon.ico" />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter"
      rel="stylesheet"
    />
  </Head>

 

각 태그의 역할은 다음과 같다.

<title>Core Web Vitals</title>

페이지의 이름을 지정한다.

 

<meta name="description" content="Core web vitals walk through" />

페이지의 중요한 정보를 검색 엔진에 전달한다.

 

<link rel="icon" href="/favicon.ico" />

파비콘 링크를 지정한다.

 

<link
  href="https://fonts.googleapis.com/css2?family=Inter"
  rel="stylesheet"
/>

스타일시트를 불러온다. 외부의 css 스타일을 적용하고 싶다면 이 기능을 활용하면 된다.

 

2. <Image /> 컴포넌트에는 alt를 지정하라.

  <Image
    src="/large-image.jpg"
    alt="Large Image"
    width={3048}
    height={2024}
  />

이미지의 alt를 지정하는 것은 중요하다.

이미지를 불러오지 못했을 경우에 해당 위치에는 다음과 같은 표시가 남는다

이때 alt가 작성되어 있다면 아래와 같이 이미지를 대체할 문구가 나타나게 된다.

 

Large Image

 

또한 시각장애인과 같이 리더기를 통해 웹에 접근해야 하는 사람들이 이미지의 정보를 파악할 수 있게 된다.

 

3. Dynamically load 

  <input
    type="text"
    placeholder="Country search..."
    className={styles.input}
    onChange={async (e) => {
      const { value } = e.currentTarget;
      // Dynamically load libraries
      const Fuse = (await import('fuse.js')).default;
      const _ = (await import('lodash')).default;

      const fuse = new Fuse(countries, {
        keys: ['name'],
        threshold: 0.3,
      });

      const searchResult = fuse
        .search(value)
        .map((result) => result.item);

      const updatedResults = searchResult.length
        ? searchResult
        : countries;
      setResults(updatedResults);

      // Fake analytics hit
      console.info({
        searchedAt: _.now(),
      });
    }}
  />

Fuse와 lodash 라이브러리를 페이지를 불러올 때 import하지 않고,

input 이벤트의 onChange가 일어날 때 dynamic import 한다.

 

간단하게 찾아보니 Fuse는 검색어 일부가 일치하더라도 검색이 되도록 하는 라이브러리다.

countries 객체의 'name'과 일치하는 검색어로 0.3 까지만큼 일치하지 않아도 찾아준다(는 뜻인 듯. 설명까진 안찾아봄)

 

      const updatedResults = searchResult.length
        ? searchResult
        : countries;
      setResults(updatedResults);

그리고 input에 입력한 값과 일치하는 결과가 있다면 setResults로 결과를 나타낸다.

 

input 이벤트 안에서 dynamic import한 라이브러리를 이용하는 방식을 처음 봤다.

4. Modal은 dynamic import 로 코드 스플리팅하여 초기 렌더링 속도를 빠르게 함

import Modal from 'react-modal';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { twilight } from 'react-syntax-highlighter/dist/cjs/styles/prism';

const customStyles = {
  content: {
    top: '50%',
    // ...스타일
  },
};

Modal.setAppElement('#__next');

export default function CodeSampleModal({ isOpen, closeModal }) {
  return (
    <Modal
      isOpen={isOpen}
      onRequestClose={closeModal}
      style={customStyles}
      contentLabel="Code Sample"
    >
      <p>Wonder no more!</p>
      <SyntaxHighlighter language="javascript" style={twilight}>
        {`function printHelloWorld() { \n  console.log('Hello World!'); \n}`}
      </SyntaxHighlighter>
      <button onClick={closeModal}>Close</button>
    </Modal>
  );
}

모달 컴포넌트를 react-modal 라이브러리를 통해 생성한 뒤,

SyntaxHighlighter도 적용하여 모달 창 안에서 코드를 하이라이팅한 모달 내용과

모달을 닫는 버튼에 클릭 이벤트로 closeModal 을 props로 전달받도록 했다.

 

react에서 modal을 쉽게 구현할 수 있는 라이브러리를 알게 됐다.

const CodeSampleModal = dynamic(() => import('../components/CodeSampleModal'), {
  ssr: false,
});

export default function Start({ countries }) {
  const [isModalOpen, setIsModalOpen] = useState(false);
  return (
  // ...
    <div className={styles.codeSampleBlock}>
      <h2 className={styles.secondaryHeading}>Code Sample</h2>
      <p>Ever wondered how to write a function that prints Hello World?</p>
      <button onClick={() => setIsModalOpen(true)}>Show Me</button>
      {isModalOpen && (
        <CodeSampleModal
          isOpen={isModalOpen}
          closeModal={() => setIsModalOpen(false)}
        />
      )}
    </div>
  );
}

 

그리고 모달의 visible 상태를 조절하는 상태를 전달한다.

이때 모달을 이용자의 클릭 이후에 나타나기 때문에,

초기 로드 속도를 빠르게 하기 위해 dynamic import로 컴포넌트 외부에서 후순위 로드가 되도록 했다.

 

이렇게 코드 스플리팅을 하여 초기 로드 속도를 빠르게 할 수 있다는 것을 배웠다.

5. 기타 : 데이터 패칭, css.module 폴더 구조

export default function Start({ countries }) {
  return (
  // ... 컴포넌트들
  );
}


export async function getServerSideProps() {
  const response = await fetch('https://restcountries.com/v3.1/all');
  const countries = await response.json();

  return {
    props: {
      countries: countries.map((country) => ({
        name: country.name.common,
        cca2: country.cca2,
        population: country.population,
      })),
    },
  };
}

Next.js 13 버전을 공부하면서 이전 버전 문법에서 살펴 본 적이 있다.

이전 버전까지는 컴포넌트와 동일한 파일 내에 getServerSideProps() 를 이용해서 비동기로 페칭하고

컴포넌트에 props로 전달하는 방식으로 데이터를 컴포넌트에 불러왔다.

 

css는 style 폴더 안에서 module로 만들고 index.js 페이지에서 불러오는 방식으로 이용한다.