๐ช
48์ผ์ฐจ
Part 15. Next.js๋ก ๋ธ๋ก๊ทธ ๋ง๋ค๊ธฐ
Ch 2. Next.js๋ก Blog ๋ชจ๋ธ ๋ง๋ค๊ธฐ
Ch 2. Next.js๋ก Blog ๋ชจ๋ธ ๋ง๋ค๊ธฐ
Next.js๋ ํ์ด๋ธ๋ฆฌ๋ ์ ์ ๋ฐ ์๋ฒ ๋ ๋๋ง, TypeScript ์ง์, ์ค๋งํธ ๋ฒ๋ค๋ง, ๊ฒฝ๋ก ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ค๊ธฐ ๋ฑ ํ๋ก๋์ ์ ํ์ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ํตํด ์ต๊ณ ์ ๊ฐ๋ฐ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. ๊ตฌ์ฑ์ด ํ์ํ์ง ์์ต๋๋ค.
๐ Next.js ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
- Manual Setup
$ npm init -y
$ npm i next react react-dom
$# ์์๋๋ก ์คํ ํ์
$ npm run dev
$ npm run build
$ npm start
// ./package.json
{
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
...
}
// ./pages/index.js
export default function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}
- create-next-app
$ npx create-next-app my-blog
$ npm run dev
๐ ๋ผ์ฐํ ์ค์ ํ๊ธฐ
- Static Routing => ์ง๊ด์
- pages/index.js → /
- pages/blog/index.js → /blog
- pages/blog/first-post.js
→ /blog/first-post - pages/dashboard/settings/username.js
→ /dashboard/settings/username
- Dynamic Routing
- pages/blog/[slug].js
→ /blog/:slug (/blog/hello-world) - pages/[username]/settings.js
→ /:username/settings (/foo/settings) - pages/post/[...all].js
→ /post/* (/post/2020/id/title)
- pages/blog/[slug].js
// ./pages/blog/[slug].js
import { useRouter } from "next/router";
export default function Blog() {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>blog/{slug}</h1>
</div>
);
}
// ./pages/[username]/setting.js
import { useRouter } from "next/router";
export default function UsernameSettings() {
const router = useRouter();
const { username } = router.query;
return (
<div>
<h1>{username}/settings</h1>
</div>
);
}
// ./pages/post/[...all].js
import { useRouter } from "next/router";
export default function PostAll() {
const router = useRouter();
const { all } = router.query;
return (
<div>
<h1>post/{all.join("/")}</h1>
</div>
);
}
// ./pages/post/[slug].js
import { useRouter } from "next/router";
export default function PostAll() {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>Post: {slug}</h1>
</div>
);
}
๐ Sanity ์ฐ๊ฒฐํ๊ณ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ โญ
- getStaticProps (Static Generation)
Fetch data at build time - getStaticPaths (Static Generation)
Specify dynamic routes to pre-render pages based on data. - getServerSideProps (Server-side Rendering)
Fetch data on each request
$ npm i @sanity/client
import { SanityClient } from "@sanity/client";
// ./services/SanityService.js
import sanityClient from "@sanity/client";
export default class SanityService {
_client = sanityClient({
dataset: "production",
projectId: "qgjogg26",
useCdn: process.env.NODE_ENV === "production",
});
async getHome() {
return await this._client.fetch(
`*[_type == 'home'][0]{'mainPostUrl': mainPost -> slug.current}`
);
}
async getPosts() {
return await this._client.fetch(`
*[_type == 'post']{
title,
subtitle,
createdAt,
'content': content[]{
...,
...select(_type == 'imageGallery' => {'images': images[]{..., 'url': asset -> url}})
},
'slug': slug.current,
'thumbnail': {
'alt': thumbnail.alt,
'imageUrl': thumbnail.asset -> url
},
'author': author -> {
name,
role,
'image': image.asset -> url
},
'tag': tag -> {
title,
'slug': slug.current
}
}
`);
}
}
// ./pages/index.js
import styles from "../styles/Home.module.css";
import SanityService from "../services/SanityService";
export default function Home({ home, posts }) {
console.log(home);
console.log(posts);
return (
<div className={styles.container}>
<h1>Blog Home</h1>
</div>
);
}
export async function getStaticProps() {
const senityService = new SanityService();
const home = await senityService.getHome();
const posts = await senityService.getPosts();
return {
props: { home, posts },
};
}
// ./pages/post/[slug].js
import SanityService from "../../services/SanityService";
export default function PostAll({ slug, post }) {
console.log(post);
return (
<div>
<h1>Post: {slug}</h1>
</div>
);
}
export async function getStaticPaths() {
const posts = await new SanityService().getPosts();
const paths = posts.map((post) => ({ params: { slug: post.slug } }));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const { slug } = params;
const posts = await new SanityService().getPosts();
const post = posts.find((p) => p.slug === slug);
return { props: { slug, post } };
}
๐ ์คํ์ผ ์์ (1) - Blog Home
$ npm i antd @ant-design/icons
$ npm i dayjs
https://nextjs.org/docs/advanced-features/custom-document
// ./pages/_app.js
import "antd/dist/antd.css";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
// ./pages/_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Roboto&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
// ./styles/globals.css
html,
body {
padding: 0;
margin: 0;
font-family: Roboto, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
// ./pages/index.js
import styles from "../styles/Home.module.css";
import SanityService from "../services/SanityService";
import Header from "../components/Header";
import BlogHeadline from "../components/BlogHeadline";
import BlogMainPost from "../components/BlogMainPost";
import BlogList from "../components/BlogList";
import Footer from "../components/Footer";
export default function Home({ home, posts }) {
console.log(home);
console.log(posts);
const mainPost = posts.find((p) => p.slug === home.mainPostUrl);
const otherPosts = posts.filter((p) => p.slug !== home.mainPostUrl);
console.log(mainPost);
console.log(otherPosts);
return (
<div className={styles.container}>
<Header />
<BlogHeadline />
<BlogMainPost {...mainPost} />
<BlogList posts={otherPosts} />
<Footer />
</div>
);
}
export async function getStaticProps() {
const senityService = new SanityService();
const home = await senityService.getHome();
const posts = await senityService.getPosts();
return {
props: { home, posts },
};
}
๐ ์คํ์ผ ์์ (2) - Post
$ npm i @sanity/block-content-to-react
$ npm i react-syntax-highlighter
// ./pages/post/[slug].js
import SanityService from "../../services/SanityService";
import styles from "../../styles/Home.module.css";
import Header from "../../components/Header";
import BlogMainPost from "../../components/BlogMainPost";
import Footer from "../../components/Footer";
import BlogPostDetail from "../../components/BlogPostDetail";
export default function PostAll({ slug, post }) {
console.log(post);
return (
<div className={styles.container}>
<Header />
<BlogMainPost {...post} />
<BlogPostDetail blocks={post.content} />
<Footer />
</div>
);
}
export async function getStaticPaths() {
const posts = await new SanityService().getPosts();
const paths = posts.map((post) => ({ params: { slug: post.slug } }));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const { slug } = params;
const posts = await new SanityService().getPosts();
const post = posts.find((p) => p.slug === slug);
return { props: { slug, post } };
}
// ./components/BlogPostDetail.jsx
import { Col, Row } from "antd";
import BlockContent from "@sanity/block-content-to-react";
import SyntaxHighlighter from "react-syntax-highlighter";
const serializers = {
types: {
code: ({ node }) => {
const { code } = node;
return (
<SyntaxHighlighter
language="javascript"
style={{
...
}}
>
{code}
</SyntaxHighlighter>
);
},
video: ({ node }) => {
return <p>video</p>;
},
link: ({ node }) => {
return <p>link</p>;
},
imageGallery: ({ node }) => {
return <p>imageGallery</p>;
},
},
};
export default function BlogPostDetail({ blocks }) {
return (
<Row>
<Col span={24}>
<BlockContent
blocks={blocks}
projectId="qgjogg26"
dataset="production"
serializers={serializers}
/>
</Col>
</Row>
);
}
๐ next.config.js
https://nextjs.org/docs/api-reference/next.config.js/introduction
// ./next.config.js
module.exports = {
trailingSlash: false,
env: {
SANITY_PROJECT_ID: "qgjogg26",
},
};
// ./services/SanityService.js
import sanityClient from "@sanity/client";
export default class SanityService {
_client = sanityClient({
dataset: "production",
projectId: process.env.SANITY_PROJECT_ID,
useCdn: process.env.NODE_ENV === "production",
});
...
}
// ./components/BlogPostDetail.jsx
...
export default function BlogPostDetail({ blocks }) {
return (
<Row>
<Col span={24}>
<BlockContent
blocks={blocks}
projectId={process.env.SANITY_PROJECT_ID}
dataset="production"
serializers={serializers}
/>
</Col>
</Row>
);
}
๐ Next.js ๋ฐฐํฌ ์ดํดํ๊ธฐ
- SSR (Server Side Rendering)
$ npm run build
$ npm start
- Static Side Generator
// ./package.json
{
...
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
},
...
}
$ npm run build
$ npx serve out
[ํ๊ธฐ]
์ด๋ฒ ์์ ์ด ๊ทธ๊ฐ ์์ ์ค์ ์ ์ผ ์ฌ๋ฏธ์๊ฒ ๋ค์ ๊ฑฐ ๊ฐ๋ค~~~! ๊ฐํธํ ๋ฐฉ๋ฒ์ผ๋ก ๋๋ฑ๋๋ฑ ๋ธ๋ก๊ทธ ๋ง๋ค ์ ์์ด์ ์ข์๋ค. ์๋กญ๊ณ ์ ๊ธฐํ์! ๋์ค์ ๋ธ๋ก๊ทธ ๋ง๋ค ์ผ ์์ ๋ ๊ผญ next js ๋ฅผ ์จ๋ณด๊ณ ์ถ๋ค.
'๐ฌ > ใ ใ ใ ใ ใ ใ ์ฑ๋ฆฐ์ง' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
49, 50์ผ์ฐจ (0) | 2022.03.30 |
---|---|
47์ผ์ฐจ (0) | 2022.03.29 |
46์ผ์ฐจ (0) | 2022.03.29 |
45์ผ์ฐจ (0) | 2022.03.28 |
43, 44์ผ์ฐจ (0) | 2022.03.26 |