๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ’ฌ/ใ…ใ……ใ…Œใ…‹ใ…ใ…… ์ฑŒ๋ฆฐ์ง€

48์ผ์ฐจ

๐Ÿช‚

48์ผ์ฐจ

 

Part 15. Next.js๋กœ ๋ธ”๋กœ๊ทธ ๋งŒ๋“ค๊ธฐ

Ch 2. Next.js๋กœ Blog ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ

 


 

 

Ch 2. Next.js๋กœ Blog ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ

 

 

https://nextjs.org/

 

Next.js by Vercel - The React Framework

Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build static and dynamic websites and web applications.

nextjs.org

 

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

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 ์—ฐ๊ฒฐํ•˜๊ณ  ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ โญ 

 

 

https://www.sanity.io/manage

 

https://www.sanity.io/manage

 

www.sanity.io

 

$ 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

 

Advanced Features: Custom `Document` | Next.js

Extend the default document markup added by Next.js.

nextjs.org

 

// ./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: Introduction | Next.js

learn more about the configuration file used by Next.js to handle your application.

nextjs.org

 

// ./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