폴더 생성 후
npx create-next-app ./ --typescript
넥스트+타입스크립트 템플릿으로 설치
기본 화면
layout.tsx
에서 favicon이나 head안의 데이터를 수정할 수 있다.
page.tsx
에서는 메인페이지의 UI를 만들 수 있다.
블로그 튜토리얼 공식문서
서버가 없기때문에
여기의 블로그 데이터를 md(mark down)파일로 만들어서 사용할것이다.
각 포스트별로 md파일을 만든다.
markdown 형식의 데이터를 일반 글로 전환하는 함수가 필요.
여러 모듈을 사용하면서 파일 읽고 replace해서 데이터로 추출하는 작업 필요.
date를 내림차순으로 정렬(sort)
lib/posts.ts
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
// posts directory 가져옴
const postsDirectory = path.join(process.cwd(), 'posts');
export function getSortedPostsData() {
const fileNames = fs.readdirSync(postsDirectory);
const allPostsData = fileNames.map(fileName => {
// fileName을 id로
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},
}
})
return allPostsData.sort((a, b) => {
if(a.date < b.date) {
return 1
} else {
return -1
}
})
}
실수로 Next.js 14버전 이상으로 설치를 해서
pages.tsx에서 작업하던 내용을 pages 폴더에 MainPage컴포넌트를 생성해 작성했다. 라우팅을 따로 해주어야하는데 서버에서 렌더되기전에 라우팅처리를 어떻게 하는지 몰라서 아직 두는중... 일단 컴포넌트만 작성.
나중에 라우팅 처리를 할 것이다..
import { getSortedPostsData } from "@/lib/posts";
import { GetStaticProps } from "next";
import Image from "next/image";
interface allPostDataProps {
allPostsData: {
date: string;
title: string;
id: string;
}[];
}
const MainPage = ({ allPostsData }: allPostDataProps) => {
console.log("test", allPostsData)
console.log("test222")
return (
<main className="flex min-h-screen flex-col items-center p-24 gap-5">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
블로그입니다.
</p>
</div>
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1] text-2xl">
JNGMNJ
</div>
<section>
<div className="text-center">
<p>[JNGMNJ Introduction]</p>
<p>(This is a website)</p>
</div>
</section>
<section className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
{allPostsData.map(({ id, title, date }) => (
<a
href=""
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
target="_blank"
rel="noopener noreferrer"
>
<h2 className={`mb-3 text-2xl font-semibold`}>
{title}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
->
</span>
</h2>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>{id}</p>
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>{date}</p>
</a>
))}
</section>
</main>
);
};
export default MainPage;
export const getStaticProps: GetStaticProps = async () => {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
};
리액트에서는 route를 위해서 react-router라는 라이브러리를 사용하지만,
Next.js에서는 페이지 개념을 기반으로 구축된 파일 시스템 기반 라우터가 있다.
파일이 페이지 디렉토리에 추가되면 자동으로 경로를 사용할 수 있다.
페이지 디렉토리 내의 파일은 가장 일반적인 패턴을 정의하는 데 사용할 수 있다.
pages/index.js 👉 "/" 경로
pages/blog/index.js 👉 "/blog" 경로
pages/blog/first-post.js 👉 "/blog/first-post"
pages/blog/[slug].js 👉 "/blog/:slug(/blog/hellow-world)"
pages/[username]/settings.js 👉 "/:username/settings"
posts/[id].tsx
import React from 'react'
const Post = () => {
return (
<div>[id]</div>
)
}
export default Post
포스트리스트 map()안에서 Link걸어준다.
리액트와 다르게 href 속성을 사용.
<Link href={
/posts/${id}}>
pages/posts/[id].tsx
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getAllPostIds();
return {
paths,
fallback: false // getStaticPaths로 리턴되지 않는 것은 모두 404page
}
}
remark와 remark-html을 이용해서 markdown을 html 변환
npm install remark remark-html --save
/lib/posts.ts
export async function getPostData(id: string) {
// matter를 이용해서 html 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;})
}
}
/pages/[id].tsx
import { getAllPostIds, getPostData } from '@/lib/posts'
import { GetStaticPaths, GetStaticProps } from 'next'
import React from 'react'
interface postDataProps {
postData: {
title: string;
contentHtml: string;
date: string;
}
}
const Post = ({ postData }: postDataProps) => {
return (
<div>
<div>
<title>{postData.title}</title>
</div>
<article>
<h1>{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로 리턴되지 않는 것은 모두 404page
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const postData = await getPostData(params.id as string);
return {
props: {
postData
}
}
}