ㅇㅇㅈ Blog

프론트엔드 수행중

0%

Next.js 공식문서 따라만들기 (2)

Next.js 공식문서 따라만들기 (2)

Assets

Next.js는 최상위 public 디렉토리에서 static assets를 관리한다

img 파일 넣기

public 디렉토리 안에 images 디렉토리를 만들고 이미지를 넣어 준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import Link from 'next/link';

const FirstPost = () => {
return (
<>
<div>FirstPost</div>
<h2>
<img src='/images/profile.jpg' alt='' />
</h2>
</>
);
};

export default FirstPost;

img src 경로가 신기하다

FirstPost 컴포넌트는 경로가 pages/posts 인데 public 안의 images 경로에 바로 접근 했다 ..

Image 컴포넌트

시매틱 img태그를 사용하면 다양한 화면 크기에서 반응, 뷰포트에 들어갈때만 이미지 로드 등등..을 수동으로 처리 해야 한다

그래서 Next.js에서 Image 컴포넌트를 제공한다
Image 컴포넌트를 사용하면 Next.js가 알아서 최적화를 해준다
Image컴포넌트를 사용하면 빌드할때 이미지를 최적화 하는 대신 사용자가 요청 할때 이미지를 최적화 한단다..
그래서 빌드할때 이미지를 10개를 하든 1천만개 이미지를 하든 빌드 시간이 증가하지 않는다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';

const FirstPost = () => {
return (
<>
<div>FirstPost</div>
<h2>
<img
src='/images/profile.jpg'
height={144}
width={144}
/>
<Image
src='/images/profile.jpg'
height={144}
width={144}
alt='profile'
/>
</h2>
</>
);
};

export default FirstPost;

사용법은 그냥 Image를 임포트하고 일반 img태그 처럼 사용하면 된다

같은 img를 사용했음에도 불구하고 파일 크기를보면 img태그와 Image컴포넌트로 만든 img의 차이가 많이 난다

이미지는 기본적으로 지연 로드됩니다. 즉, 뷰포트 외부의 이미지에 대해서는 페이지 속도에 영향을 미치지 않습니다. 이미지는 뷰포트로 스크롤될 때 로드됩니다.

Head 태그

일반적 HTML의 head태그에 들어가는 정보들을 Head 를 통해 작성 해 줄 수 있다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Head from 'next/head';
import Link from 'next/link';
export default function Home() {
return (
<div className='container'>
<Head>
<title>My page title</title>
<meta property='og:title' content='My page title' key='title' />
</Head>
<h1>
<ul>
<Link href='/posts/first-post'>
<li>this Page</li>
</Link>
<Link href='/posts/Hello'>
<li>Hello World</li>
</Link>
</ul>
</h1>
</div>
);
}

헤드 태그 안에 정보들이 들어가 있다

두 가지 형태의 사전 렌더링

NEXT는 브라우저에 렌더링 할 때 기본적으로 pre-rendering을 한다.
pre-rendering이란 각 페이지들을 사전에 미리 HTML 문서로 생성하여 가지고 있는 것 그리고 서버로 요청이 들어올 때 알맞은 페이지를 반환해준다.
그 중 두가지 형태의 사전렌더링이 있다
👉 Static-Generation(SSG)

  • HTML을 빌드 타임에 각 페이지별로 생성하고 해당 페이지에 요청이 올 때 미리 생성된 HTML문서를 반환
  • 서버에서 페이지가 생성이 되는게 아니라 빌드할때 생성된 문서를 보여주는것이므로 속도가 빠르다
  • 공식문서에서는 마케팅페이지/ 블로그게시물/ 도움말 및 문서 등을 페이지로 사용할때 SSG 사용을 권하고있다

👉 Server-side Rendering(SSR)

  • 서버로 요청이 올 때마다 해당하는 HTML 문서를 그때 그때 생성하여 반환
  • SSR방식은 서버에서 요청을 받을때마다 그에 상응하는 HTML을 생성하여 반환하기 때문에 항상 최신 상태를 유지해야하는 경우가 적합하다 (좋아요 표시라든지)

getStaticProps

블로그에 데이터를 추가하고 불러오기

최상위 경로에 posts directory를 만들고 안에 블로그 게시물을 md문서로 만든다

공식문서에서 제공하는 글을 그대로 썻다

1
2
3
4
5
6
7
8
9
10
11
---
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---

Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.

- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.

Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

이 마크다운 문서에서 id(파일이름),title,datet를 불러와 index페이지에 렌더링

먼저 마크다운 파일의 데이터를 분석할수 있는 모듈을 설치

1
$ npm i gray-matter

그리고 파일 시스템에서 데이터를 가져오기 위한 라이브러리를 만들어 준다

최상위 경로에 lib라는 directory를 만들고 안에 posts.js를 만든다음 작성해준다
getSortedPostsData함수를 만들어서 export 해준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');
// ✅ process.cwd()로 경로를 찾고

export function getSortedPostsData() {
// ✅ posts안의 파일이름들을 fileNames 배열에 저장하는거 같다
const fileNames = fs.readdirSync(postsDirectory);

const allPostsData = fileNames.map((fileName) => {
// ✅ 파일이름에서 .md 를 정규식으로 제거 해주고 id에 저장
const id = fileName.replace(/\.md$/, '');

// ✅ path.join으로 경로를 만들어준다 postsDirectory는 'posts' 이고
// fileName은 말 그대로 파일명에서 .md를 제거한 이름 둘을 합쳐서 fullPath에 저장 그럼 fullPath = /posts/파일명 이 된다
const fullPath = path.join(postsDirectory, fileName);

// ✅ 이건 컨텐츠 내용을 저장하는거 같은데 파일 경로에서..utf8로 엔코딩 해주고 파일시스템 메소드로 어떻게 저장해주는거 같다..
const fileContents = fs.readFileSync(fullPath, 'utf8');

// ✅ gray-metter 로 컨텐츠를 어떻게 변환해주는 과정 같다
const matterResult = matter(fileContents);

// ✅ id 와 data를 return
return {
id,
...matterResult.data,
};
});
// Sort posts by date
return allPostsData.sort(({ date: a }, { date: b }) => {
if (a < b) {
return 1;
} else if (a > b) {
return -1;
} else {
return 0;
}
});
}

✅ process.cwd()는 찾아보니 node모듈에서 경로를 찾는 메소드 인거 같다
__dirname과 차이점을 찾아보니
process.cwd()는 노드 프로세스를 실행하는 디렉토리의 값을 반환
__dirname은 현재 실행중인 파일이 있는 디렉토리 값을 반환 한단다..
https://stackoverflow.com/questions/9874382/whats-the-difference-between-process-cwd-vs-dirname

다음 index.js에서 getStaticPropsgetSortedPostsData()를 호출한다

1
2
3
4
5
6
7
8
9
10
import { getSortedPostsData } from '../lib/posts';

export async function getStaticProps() {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
}

여기서 props는 Home 컴포넌트에 넘겨주면 된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Head from 'next/head';
import Layout from '../components/layout';
import utilStyles from '../styles/utils.module.css';
import { getSortedPostsData } from '../lib/posts';
import Link from 'next/link';

export default function Home({ allPostsData }) {
return (
<Layout home>
<Head>
<title>My page title</title>
<meta property='og:title' content='My page title' key='title' />
</Head>

/* 아래는 작성 되는 부분 */
<section >
<h2 className={utilStyles.headingLg}>Blog</h2>
<ul className={utilStyles.list}>
{allPostsData.map(({ id, date, title }) => (
<li className={utilStyles.listItem} key={id}>
<a>{title}</a>
<br />
{date}
</li>
))}
</ul>
</section>
</Layout>
);
}

getStaticProps에서 return하는 props를 가져와 출력해준다
id는 파일이름이다

동적 경로 getStaticPaths 구현

페이지 경로가 외부 데이터에 의존하는 경우 Next.js를 사용하면 외부 데이터에 의존하는 경로가 있는 페이지를 정적으로 생성할 수 있다.

👉 블로그 게시물에 대한 동적 경로 만들기

  • 각 게시물에 경로가 있어야한다 /posts/<id> 여기서 <id>는 최상위 posts 디렉토리 안에 있는 MD파일의 이름
  • ssg-ssr.md , pre-rendering.md 가 있으므로 /posts/ssg-ssr , /posts/pre-rendering이 경로가 되어야 한다

[id].js라는 파일을 pages/posts디렉토리 안에 생성해준다 여기서 [id]가 경로가 될 것이다

그리고 id에 맞게 게시물 페이지를 렌더링 코드를 [id].js 에 작성

1
2
3
4
5
import Layout from '../../components/layout';

export default function Post() {
return <Layout>...</Layout>;
}

[id].js는 게시물 경로로 들어가면 보여지는 페이지이다

[id].js를 작성하기 이전에

postsId를 가져 올 수 있게 lib/posts.js안에 작성 getAllPostsIds함수를 만들어서 export 해준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// lib/posts.js

export function getAllPostIds() {
const fileNames = fs.readdirSync(postsDirectory);

// Returns an array that looks like this:
// [
// {
// params: {
// id: 'ssg-ssr'
// }
// },
// {
// params: {
// id: 'pre-rendering'
// }
// }
// ]
return fileNames.map((fileName) => {
return {
// ✅ 파일이름으로 params를 구성하고 return 하고 있다
params: {
id: fileName.replace(/\.md$/, ''),
},
};
});
}

다음 [id].jsgetAllPostIds()를 가져와 getStaticPaths안에서 사용해준다

1
2
3
4
5
6
7
8
9
10
11
//[id].js

import { getAllPostIds } from '../../lib/posts';

export async function getStaticPaths() {
const paths = getAllPostIds();
return {
paths,
fallback: false,
};
}

게시물 페이지 렌더링 getStaticProps 구현

lib/posts.jsgetPostData함수를 만들어서 export 해준다

1
2
3
4
5
6
7
8
9
10
11
12
export function getPostData(id) {
//
const fullPath = path.join(postsDirectory, `${id}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');

const matterResult = matter(fileContents);

return {
id,
...matterResult.data,
};
}

MD문서의 컨텐츠 부분을 html로 파싱해주기 위해 모듈 설치

1
$ npm i remark remark-html

lib/posts.js 에 import 해주고 getPostData()에 추가 작성 해준다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { remark } from 'remark';
import html from 'remark-html';

export async function getPostData(id) {
const fullPath = path.join(postsDirectory, `${id}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');

const matterResult = matter(fileContents);

// 추가 작성되는 부분
const processedContent = await remark()
.use(html)
.process(matterResult.content);
const contentHtml = processedContent.toString();

return {
id,
contentHtml,
...matterResult.data,
};
}

[id].js에서 getStaticProps으로 getPostData()를 호출하고 data를 렌더링 해주면 된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import { getAllPostIds, getPostData } from '../../lib/posts';
import utilStyles from '../../styles/utils.module.css';

export default function Post({ postData }) {
return (
<Layout>
<Head>
<title>{postData.title}</title>
</Head>
<article>
<h1 className={utilStyles.headingXl}>{postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={postData.date} />
</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
// ✅ dangerouslySetInnerHTML
</article>
</Layout>
);
}

export async function getStaticPaths() {
const paths = getAllPostIds();
return {
paths,
fallback: false,
};
}

export async function getStaticProps({ params }) {
const postData = await getPostData(params.id);
return {
props: {
postData,
},
};
}

✅ dangerouslySetInnerHTML는 React에서 innerHTML을 사용하기 위한 방법이다 __html키로 값을 전달 해야한다

localhost:3000/posts/ssg-ssr 로 접속해보면

컨텐츠도 잘 출력 된다