Nextjsと概念APIによって供給されるブログを構築してください


  • YouTubeチュートリアルhttps://youtu.be/LFRYYIoiIZg
  • ブログ例https://nextjs-notion-blog-chi.vercel.app/

  • 導入
    それは私の個人的な生活になるの概念ゲームチェンジャーされています.それは私の目標を文書化からすべてを管理することができます私の考えをジャーナリング.このため、私は概念を使用して私の個人的なブログのWordPressのようなツールを介してこれまでの概念を残していないの利便性の電源を使用します.このチュートリアルでは、NextJS & TailWindCSSと連動して、あなたのブログに電源を入れるために、どのようにNotionAPIを使用できるかを示します.

    設定の概念
    あなたがこのチュートリアルのために彼らの自由な層を使うことができる点に注意してください.

    概念統合の作成
    移動するhttps://www.notion.so/my-integrations と新しい内部の統合を作成する

    データベースの作成

    テンプレートを複製することができますhere .

    ブログへのグラント統合アクセス
    共有ボタンをクリックし、統合アクセスを行います.


    プロジェクト作成

    アプリケーションを作成する
    $ npx create-next-app mysite --typescript
    

    インストールする
    npm install -D tailwindcss postcss autoprefixer @tailwindcss/typography
    npx tailwindcss init -p
    

    セットアッププロジェクト

    を設定します
    あなたのtailwind.config.js ファイルを追加し、次のように追加します.
    module.exports = {
        content: [
            "./pages/**/*.{js,ts,jsx,tsx}",
            "./components/**/*.{js,ts,jsx,tsx}",
        ],
        theme: {
            extend: {},
            fontFamily: {
                sans: ["'Montserrat'"],
                mono: ["'Inconsolata'"]
            }
        },
        plugins: [
            require('@tailwindcss/typography')
        ],
    }
    

    グローバル風にCookieを追加します.CSSファイル
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    

    ドキュメントを追加します.TSX
    カスタムフォントを使用するには、新しいファイルを作成する必要がありますpages/_document.tsx を参照してください
    import Document, {Html, Head, Main, NextScript, DocumentContext} from 'next/document'
    
    class MyDocument extends Document {
        static async getInitialProps(ctx: DocumentContext) {
            const initialProps = await Document.getInitialProps(ctx)
            return {...initialProps}
        }
    
        render() {
            return (
                <Html>
                    <Head>
                        <link rel="preconnect" href="https://fonts.googleapis.com"/>
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin={'true'}/>
                        <link
                            href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@200;300;400;500;600;700;800;900&family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
                            rel="stylesheet"/>
                    </Head>
                    <body>
                    <Main/>
                    <NextScript/>
                    </body>
                </Html>
            )
        }
    }
    
    export default MyDocument
    

    追加.envファイル
    新しいファイルを作成する.env.local 次の情報を使用します.
    NOTION_ACCESS_TOKEN=
    NOTION_BLOG_DATABASE_ID=
    
    のためにNOTION_ACCESS_TOKEN 我々は統合し、秘密キーをコピーに行くことができます

    のためにNOTION_BLOG_DATABASE_ID UUIDをURL内でコピーできます


    タイプファイルの追加
    新しいファイルを作成する@types/schema.d.ts 次の行を追加します.
    export type Tag = {
        color: string
        id: string
        name: string
    }
    
    export type BlogPost = {
        id: string;
        slug: string;
        cover: string;
        title: string;
        tags: Tag[];
        description: string;
        date: string
    }
    

    プロジェクトのビルド

    クライアントをインストール
    私たちは、ブログのデータとディスプレイ用の他のパッケージを得るために、JavaScriptクライアントという概念をインストールする必要があります
    npm install @notionhq/client notion-to-md react-markdown
    

    カスタム概念サービス
    import {Client} from "@notionhq/client";
    import {BlogPost, PostPage} from "../@types/schema";
    import {NotionToMarkdown} from "notion-to-md";
    
    export default class NotionService {
        client: Client
        n2m: NotionToMarkdown;
    
        constructor() {
            this.client = new Client({ auth: process.env.NOTION_ACCESS_TOKEN });
            this.n2m = new NotionToMarkdown({ notionClient: this.client });
        }
    
        async getPublishedBlogPosts(): Promise<BlogPost[]> {
            const database = process.env.NOTION_BLOG_DATABASE_ID ?? '';
            // list blog posts
            const response = await this.client.databases.query({
                database_id: database,
                filter: {
                    property: 'Published',
                    checkbox: {
                        equals: true
                    }
                },
                sorts: [
                    {
                        property: 'Updated',
                        direction: 'descending'
                    }
                ]
            });
    
            return response.results.map(res => {
                return NotionService.pageToPostTransformer(res);
            })
        }
    
        async getSingleBlogPost(slug: string): Promise<PostPage> {
            let post, markdown
    
            const database = process.env.NOTION_BLOG_DATABASE_ID ?? '';
            // list of blog posts
            const response = await this.client.databases.query({
                database_id: database,
                filter: {
                    property: 'Slug',
                    formula: {
                        text: {
                            equals: slug // slug
                        }
                    },
                    // add option for tags in the future
                },
                sorts: [
                    {
                        property: 'Updated',
                        direction: 'descending'
                    }
                ]
            });
    
            if (!response.results[0]) {
                throw 'No results available'
            }
    
            // grab page from notion
            const page = response.results[0];
    
            const mdBlocks = await this.n2m.pageToMarkdown(page.id)
            markdown = this.n2m.toMarkdownString(mdBlocks);
            post = NotionService.pageToPostTransformer(page);
    
            return {
                post,
                markdown
            }
        }
    
        private static pageToPostTransformer(page: any): BlogPost {
            let cover = page.cover;
            switch (cover) {
                case 'file':
                    cover = page.cover.file
                    break;
                case 'external':
                    cover = page.cover.external.url;
                    break;
                default:
                    // Add default cover image if you want...
                    cover = ''
            }
    
            return {
                id: page.id,
                cover: cover,
                title: page.properties.Name.title[0].plain_text,
                tags: page.properties.Tags.multi_select,
                description: page.properties.Description.rich_text[0].plain_text,
                date: page.properties.Updated.last_edited_time,
                slug: page.properties.Slug.formula.string
            }
        }
    }
    

    インデックスファイル
    最初に、私たちはstaticProps 方法:
    import {GetStaticProps, InferGetStaticPropsType} from "next";
    import Head from "next/head";
    import {BlogPost} from "../@types/schema";
    import NotionService from "../services/notion-service";
    
    export const getStaticProps: GetStaticProps = async (context) => {
        const notionService = new NotionService();
        const posts = await notionService.getPublishedBlogPosts()
    
        return {
            props: {
                posts
            },
        }
    }
    
    const Home = ({posts}: InferGetStaticPropsType<typeof getStaticProps>) => {
        const title = 'Test Blog';
        const description = 'Welcome to my Notion Blog.'
    
        return (
            <>
                <Head>
                    <title>{title}</title>
                    <meta name={"description"} title={"description"} content={description}/>
                    <meta name={"og:title"} title={"og:title"} content={title}/>
                    <meta name={"og:description"} title={"og:description"} content={title}/>
                </Head>
    
                <div className="min-h-screen">
                    <main className="max-w-5xl mx-auto relative">
                        <div className="h-full pt-4 pb-16 px-4 md:px-0 mx-auto">
                            <div className="flex items-center justify-center">
                                <h1 className="font-extrabold text-xl md:text-4xl text-black text-center">Notion + NextJS Sample Blog</h1>
                            </div>
                            <div className="mt-12 max-w-lg mx-auto grid gap-5 lg:grid-cols-2 lg:max-w-none">
                                {posts.map((post: BlogPost) => (
                                    <p key={post.id}>Blog Post Component Here: {post.title}</p>
                                ))}
                            </div>
                        </div>
                    </main>
                </div>
            </>
        )
    };
    
    export default Home;
    

    ブログカードコンポーネント
    次に、ブログカードのコンポーネントを作成します
    最初に日付をモーフィング日付のインストール
    $ npm install dayjs
    
    ファイルを作成するcomponents/BlogCard.tsx
    import {FunctionComponent} from "react";
    import Link from "next/link";
    import {BlogPost} from "../@types/schema";
    import dayjs from 'dayjs'
    
    type BlogCardProps = {
        post: BlogPost
    }
    const localizedFormat = require('dayjs/plugin/localizedFormat');
    dayjs.extend(localizedFormat)
    
    const BlogCard: FunctionComponent<BlogCardProps> = ({post}) => {
    
        return (
            <Link href={`/post/${post.slug}`}>
                <a className="transition duration-300 hover:scale-105">
                    <div key={post.title} className="flex flex-col rounded-xl shadow-lg overflow-hidden">
                        <div className="flex-shrink-0">
                            <img className="h-64 w-full object-fit" src={post.cover} alt="" />
                        </div>
                        <div className="flex-1 bg-gray-50 pt-2 pb-6 px-4 flex flex-col justify-between">
                            <div className="flex-1">
                                <span className="block mt-2">
                                    <h4 className="text-xs font-medium text-gray-600">{dayjs(post.date).format('LL')}</h4>
                                </span>
                                <span className="block mt-2">
                                    <h3 className="text-xl font-semibold text-gray-900">{post.title}</h3>
                                </span>
    
                                <span className="block mt-2">
                                    <p className="text-sm text-gray-600">{post.description}</p>
                                </span>
    
                                <span className="block mt-2 space-x-4">
                                    {
                                        post.tags.map(tag => (
                                            <span key={tag.id} className='bg-green-300 text-green-800 px-2 py-1 text-xs rounded-lg'>
                                                                            #{tag.name}
                                                                        </span>
                                        ))
                                    }
                                </span>
                            </div>
                        </div>
                    </div>
                </a>
            </Link>
        );
    };
    
    export default BlogCard;
    
    置換
    <p>Blog Post Component Here: {post.title}</p>
    
    with
    import BlogCard from "../components/BlogCard";
    
    <BlogCard key={post.id} post={post}/>
    
    インデックスファイルで.

    ポストファイル
    次に、1つのブログ記事を表示するページを作成しますpost/[slug].tsx ここで我々は動的パラメータを作ります.
    💡 両方を利用することになりますgetStaticPaths and getStaticProps これは、静的なパスを生成しているときに、概念を変更するたびに、サイトを再配備しなければならないことを意味します.
    import {GetStaticProps, InferGetStaticPropsType} from "next";
    import ReactMarkdown from "react-markdown";
    import Head from "next/head";
    import NotionService from "../../services/notion-service";
    
    const Post = ({markdown, post}: InferGetStaticPropsType<typeof getStaticProps>) => {
        return (
            <>
                <Head>
                    <title>{post.title}</title>
                    <meta name={"description"} title={"description"} content={post.description}/>
                    <meta name={"og:title"} title={"og:title"} content={post.title}/>
                    <meta name={"og:description"} title={"og:description"} content={post.description}/>
                    <meta name={"og:image"} title={"og:image"} content={post.cover}/>
                </Head>
    
                <div className="min-h-screen">
                    <main className="max-w-5xl mx-auto relative">
                        <div className="flex items-center justify-center">
                            <article className="prose">
                                <ReactMarkdown>{markdown}</ReactMarkdown>
                            </article>
                        </div>
                    </main>
                </div>
    
            </>
        )
    }
    
    export const getStaticProps: GetStaticProps = async (context) => {
        const notionService = new NotionService()
    
        // @ts-ignore
        const p = await notionService.getSingleBlogPost(context.params?.slug)
    
        if (!p) {
            throw ''
        }
    
        return {
            props: {
                markdown: p.markdown,
                post: p.post
            },
        }
    }
    
    export async function getStaticPaths() {
        const notionService = new NotionService()
    
        const posts = await notionService.getPublishedBlogPosts()
    
        // Because we are generating static paths, you will have to redeploy your site whenever
        // you make a change in Notion.
        const paths = posts.map(post => {
            return `/post/${post.slug}`
        })
    
        return {
            paths,
            fallback: false,
        }
    }
    
    export default Post;
    

    回収する
    結論では、概念はあなたのCMSのアプリケーションを置き換えるために使用できる強力なツールです.あなたがこのチュートリアルを役に立つならば、私が私のYouTubeチャンネルに加入することを考えて、私は記録します
    定期的に、またはTwitter上で私に従ってプログラミングのコンテンツ.

    社会
    Github
    Patreon