본문 바로가기

카테고리 없음

Inflearn 7주차 - Next.js와 TypeScript

 

[SSR vs CSR]

1) Client Side Rendering

 

서버에서 처리 없이 클라이언트로 보내주기 때문에 JavaScript가 모두 다운로드 완료되고, 실행을 마치기 전까지는 사용자는 빈 화면을 보고 있게 된다. 

 

*CSR이 왜 좋은가?

SEO 대응에 용이하다.

+) SEO 대응에 대한 추가적인 설명

SEO 대응 
검색 엔진은 자동화된 로봇인 '크롤러'로 웹 사이트들을 읽는다. CSR은 자바스크립트를 실행시켜 동적으로 컨텐츠가 생성되기 때문에 자바스크립트가 실행 되어야 meatadata가 바뀌었다. 
(이전 크롤러들은 자바스크립트를 실행시키지 않았었기에 SEO 최적화가 필수적이었다. 구글이 그 트렌드를 바꾸고 있다고 한다.) 

SSR은 애초에 서버 사이드에서 컴파일되어 클라이언트로 넘어오기 때문에 크롤러에 대응하기 용이하다.

출처: https://hahahoho5915.tistory.com/52 [넌 잘하고 있어:티스토리]

 

2) Server Side Rendering

서버에서 이미 렌더링이 상태로 클라이언트에 전달되기 때문에, 

JS가 다운로드 되는 동안 사용자는이미 화면을 보고 있을 수 있다.

 

 

Next.js 모듈 설치

TypeScript, ESLint 등에 대한 부가적 옵션은 아래와 같이 설정한다.

npx create-next-app ./

 

npx create-next-app@latest

실행은 "npm run dev"로 !!!

[기본적인 폴더 구조]

 

 

*pages: 이 폴더 안에 페이지들을 생성

-> index.tsx가  첫 '/' 페이지로 설정됨.

->app.tsx는 공통되는 레이아웃을 작성함. 모든 페이지에 공통으로 들어가는 것을 넣어주는 곳.

즉, url을 통해 특정 페이지에 진입하기 전 통과하는 인터셉터 페이지다.

*public: 이미지같은 정적 에셋들 보관

styles: 말그대로 스타일링 처리해주는 폴더

*next.config.js: 환경설정, 세팅하는 곳

 

[Pre-rendering]

NextJS는 모든 페이지를 pre-rendering함.

즉, 모든 페이지를 위한 HTML을 클라이언트 사이드에서 자바스크립트로 처리하기 전, '사전에 생성한다'는 의미이다.

->SEO 검색엔진 최적화가 좋아짐.

 

Q. Next.js는 pre-rendering을 하는데, 이걸 하면 왜 좋을까?

초기 로딩이 빠르고, 검색엔진 최적화에 좋다. Pre-rendering된 페이지는 서버 측에서 이미 HTML로 렌더링되어 있으므로 검색 엔진은 이를 빠르게 인식하고 색인화할 수 있다. 이로 인해 페이지가 검색 결과에서 더 잘 나타날 가능성이 높아진다.

 

[Data Fetching]

Nextjs에서 데이터를 가져오는 방법에 대해 이야기해보자.

보통 리액트에서는 useEffect 안에서 가져오는데, Nextjs에는 다른 방법으로 가져옴!

 

1) getStaticProps: 빌드할 때 데이터를 불러옴.(Static Generation)

->async로 export하면 getStaticProps에서 리턴되는 props를 가지고 페이지를 pre-rendering함.

 

2) getStaticPaths: 동적 라우팅이 필요할때 getStaticPaths로 경로 리스트를 정의하고 HTML에 build 시간에 렌더링 됨.

->어떤 경로가 pre-render될지를 결정함.

-> getServerSideProps함수를 async로 export하면, Next는 각 요청마다 리턴되는 데이터를 getServerSideProps로 pre-r

 

3) getServerSideProps:요청이 있을때 데이터를 불러옴. 요청할 때 데이터를 가져와야하는 페이지를 미리 렌더해놔야 할 때 사용한다.

getServerSideProps 함수를 async로 export 하면, Next는 각 요청마다 리턴되는 데이터를 getServerSideProps로 pre-render한다.

 

[TypeScript란?]

자바스크립트의 확장된 버전.

기존 자바스크립트에서는 타입 지정이 없던 부분을 언어에 추가해 확장한 버전.

개발시 버그 잡는 것을 도와줌.

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

[md 파일이란?]

Github의 README파일이 바로 md 파일이다.

텍스트 기반의 마크업 언어로, HTML로 변환 가능. 마크다운이 최근 각광받기 시작한 것은 README 때문이다.

 

[gray-matter]

npm install --save gray-matter 명령어 터미널에 입력해 설치

파일컨텐츠는 matter안에 넣어주면,

md파일을 객체로 convert해줌.

 

[sort method]

[Markdown 파일 데이터로 추출하기]

nextjs-app 폴더 > lib 폴더 > post.ts 파일 작성

아까 posts에 작성했던 md 파일을 데이터로 추출하기 위한 코드를 작성한 것이다.

import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'

/*경로 연결*/
const postsDirectory=path.join(process.cwd(),'posts');

    export function getSortedPostsData(){
    // /posts 파일 이름 잡아주기
    const fileNames=fs.readdirSync(postsDirectory);
    //['pre-rendering.md', ...]
    const allPostsData=fileNames.map(fileName=>{
        const id=fileName.replace(/\.md$/,"");
        const fullPath=path.join(postsDirectory,fileName);
        const fileContents=fs.readFileSync(fullPath,'utf-8');
        const matterResult=matter(fileContents);

        return {
            id,
            ...(matterResult.data as {date:string;title:string})
        }
    })
    //Sorting
    return allPostsData.sort((a,b)=>{
        if(a.date<b.date){
            return 1;
        }else{
            return -1;
        }
    })
}

 

[TypeScript란?]

자바스크립트의 슈퍼셋인 오픈소스 프로그래밍 언어이다. 마이크로소프트에서 개발, 유지하고 있으며 엄격한 문법을 지원한다. JavaScript에서 제공하는 기본 제공 유형을 상속한다. 변수 사용시 반드시 타입을 명시해줘야 한다.

 

 

[TypeScript Type]

Type이란, 그 value가 가지고 있는 property나 function을 추론할 수 있는 방법이다.

ex) "apple"의 type은 string이다.

->즉,"apple"은 .length라는 property와 .toLowerCase() 등의 function을 가진다고 추론할 수 있다!

 

 

1) 기본 제공 타입

타입스크립트에서 제공되는 타입 유형은 크게 두가지이다.

-Primitive Types

-Opject Types

 

2) 추가 제공 타입

위의 제공 타입 외에도 아래와 같은 추가적인 제공타입을 제공한다.

-Any: 타입 검사 없이 통과. 잘 알지 못하는 타입을 표현해야할 때 사용.

사용자로부터 받은 같 or 서드 파티 라이브러리같은 동적인 컨텐츠에서 오는 경우 주로 사용.

-Union: 변수 또는 함수 매개변수에 대해 둘 이상의 데이터 유형을 사용할 수 있게 함.(|->or 연산자 사용해서)

-Tuple: 배열 타입을 보다 특수한 형태로 사용 가능. tuple의 경우, 명시적으로 지정된 형식에 따라 아이템 순서를 정해야되고, 추가되는 아이템 또한 tuple에 명시된 타입만 사용할 수 있다.

ex) var student:[string, number]=["sg", 1];로 정의되어있는데 student.push(true) 이런식으로 값 추가하면 Error 남.

-Enum: 열거형을 의미함. 어려운 숫자 대신 친숙한 이름으로 element를 작성하는데 열거된 각 요소들은 별도의 값이 사용되지 않은 경우 0부터 시작하여 순서대로 +1씩 증가하며 값을 할당받는다.

enum element에서  =(assignment)를 사용해 명시적으로 값을 지정해 줄 수도 있음.

*Enum과 Object의 차이점은?

Enum은 Object와 다르게 선언할 때 이후에 값을 추가하거나 변경할 수 없다.

Object의 속성값은 JS가 허용하는 모든 타입이 올수 있지만 enum의 속성값으로는 문자열 혹은 숫자만 올 수 있다.

-void: 함수가 값을 반환하지 않을 시 반환 유형으로 void를 지정한다.

-Never: 절대 발생하지 않을 값을 나타내는 타입이다. 예를 들면 무한 loop를 발생시켜 리턴값을 절대 내보내지 않거나(심지어 void마저도!!!), 반드시 Error만을 throw하는 경우  Never를 사용한다. 

 

*Void와 Never의 차이점?

void는 값으로 undefined나 null을 가질 수 있지만, never는 어떤 값도 가질 수 없는 경우이다.

ex) let nothing: never = null;이라는 코드는 Error를 발생시킨다.(let something: void = null; 은 가능)

 

3) Type annotation

개발자가 타입을 타입스크립트에게 직접 말해주는 것이다.

cons rate: number = 5;

 

4) Type inference

타입스크립트가 알아서 타입을 추론하는 것이라고 할 수 있다.

단, Type inference는 변수 선언과 초기화를 동시에 하는 경우만 사용할 수 있다.

변수 선언 하고 나중에 값 할당하는 경우엔 Type annotation을 안해주면 Error가 난다!!!!!

ex) const rate = 5;

 

5) Type Annotation

일반적인 경우는  Type inference를 활용해 타입 annotation을 꼭 안해줘도 된다. 

단. 타입을 추론하지 못해서 타입 Annotation을 꼭 해줘야하는 경우들이 있다.

 

1. any 타입을 리턴하는 경우

리턴타입이 일정하지 않으므로 Any를 리턴한다고 타입 애노테이션을 해줘야 함.

2. 변수 선언을 먼저 하고 나중에 초기화 하는 경우

3. 변수에 대입될 값이 일정치 못하는 경우

-> 여러 타입이 지정되어야 하는 경우 |(or)을 이용해 여러 타입을 annotation 해준다.

6) Type assetion

프로그래머가 컴파일러에게 "내가 너보다 타입에 대해 더 잘 알고 있으니 내 주장에 토 달지 마!!" 라는 의미이다.

이를 사용하면 값의 type을 설정하고 컴파일러에게 이를 유추하지 않도록 지시할 수 있다.

Error 나는 경우

 

Type Assertion 수행한 경우

 

---------------------------------------------------------------------------------------------------------------------------------------------

실습파트

---------------------------------------------------------------------------------------------------------------------------------------------

[getStaticProps를 이용한 포스트 리스트 나열]

import type { NextPage } from 'next'
import Head from 'next/head'
import homeStyles from '../styles/Home.module.css'
import { GetStaticProps } from 'next'
import { getSortedPostsData } from '../lib/post'
import Link from 'next/link'

const Home = ({ allPostsData }: {
  allPostsData: {/*allPostsData에 대한 타입 정의*/
    date: string
    title: string
    id: string
  }[]
}) => {
  return (
    <div>
      <Head>
        <title>Your Name</title>
      </Head>
      <section className={homeStyles.headingMd}>
        <p>[Your Self Introduction]</p>
        <p>
          (This is a website)
        </p>
      </section>
      <section className={`${homeStyles.headingMd} ${homeStyles.padding1px}`}>
        <h2 className={homeStyles.headingLg}>Blog</h2>
        <ul className={homeStyles.list}>
          {allPostsData.map(({id, title, date}) =>
          <li className={homeStyles.listTiem} key={id}>
            <a>{title}</a>
            <br />
            <small className={homeStyles.lightText}>
              {date}
            </small>
          </li>
          )}
        </ul>
      </section>
    </div>
  )
}

export default Home

export const getStaticProps: GetStaticProps = async()=>{
  const allPostsData=getSortedPostsData();
  return {
    props:{
      allPostsData
    }
  }
}

 

[포스트 자세히 보기 페이지로 이동 구현]

리액트에선 react-router라는 라이브러리를 이용하지만, Next.js는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다. 파일이 페이지 디렉토리에 추가되면 자동으로 경로로 사용할 수 있다고 한다.

pages폴더 밑에 > posts폴더 > [id].tsx 파일을 생성해 이동할 파일을 지정하였다.

이동할 페이지 생성

->index.tsx에 Link 컴포넌트를 연결해 페이지가 이동되도록 하였다. 

빨간줄이 그어진 해당 링크를 누르면, 위에서 생성했던 [id].tsx 상세 페이지로 이동한다.

[id].tsx 페이지

 

[포스트 데이터 가져와서 보여주기] - remark

*우선 remark 사용하기 위해선 모듈 설치해주고 파일에 remark를 import 해줘야한다.

import {remark} from 'remark';
import remarkHtml from 'remark-html

 

[post.ts 파일]

1) post.ts 파일에 fileNames 가져오는 함수 작성


export function getAllPostIds(){
    const fileNames = fs.readdirSync(postsDirectory);
    return fileNames.map(fileName => {
        return {
            params:{
                id:fileName.replace(/\.md$/, '')
            }
        }
    })
}

2) getPostData 함수 작성

export async function getPostData(id:string){
    const fullPath=path.join(postsDirectory, `${id}.md`)
    const fileContents=fs.readFileSync(fullPath, 'utf-8')

    const matterResult=matter(fileContents);

    const processedContent=await remark().use(remarkHtml).process(matterResult.content);
    const contentHtml = processedContent.toString();
    return {
        id,
        contentHtml,
        ...(matterResult.data as {date:string, title:string})
    }
}

 

[ [id].tsx 파일 ]

 

 

import React from 'react'
import Head from 'next/head'
import {GetStaticPaths, GetStaticProps} from 'next'
import homeStyles from '../../styles/Home.module.css';
import {getAllPostIds, getPostData} from '../../lib/post'
const Post = ({postData}:{
    postData:{
        title:string
        date:string
        contentHtml:string
    }
}) => {
    return(
        <div>
            <Head>
                <title>{postData.title}</title>
            </Head>
            <article>
                <h1 className={homeStyles.headingXl}>{postData.title}</h1>
                <div>
                    {postData.date}
                </div>
                <div dangerouslySetInnerHTML={{__html: postData.contentHtml }} />
            </article>
        </div>
    )
}
export default Post;


export const getStaticPaths:GetStaticPaths=async()=>{
    const paths=getAllPostIds();
    return {
        paths,
        fallback:false/*getStaticPaths로 리턴되지 않는 것들은 모두 404 Error 발생*/
    }
}


export const getStaticProps:GetStaticProps=async({params})=>{
    const postData=await getPostData(params.id as string)
    return {
        props:{


            postData


        }


    }
}