herohoroブログ

NotionAPIで取得したslugの値で記事リンクを作成する | getStaticPathとDynamic routesが便利だ_Blog learn04



🔄   2024-02-29

ブログ用に用意しているNotionデータベースにslug列を追加します。

image block

作りたいのはこんな感じ

やること
  1. NotionAPIを使ってslugを表示させる
  2. slugを記事毎のURLとして割り振る
  3. 割り振ったURLを記事タイトルに埋め込む

NotionAPIを使ってslugを表示させる

表示させるのは前回の記事を元にするとすぐ実装できそう⭐

https://herohoro.com/blog/blog-learn_notion-api-read

やることは4つ
  • 読み込む列typeに合わせた型をlib/interfaces.tsに追加
  • propsで使うpostにslugデータをlib/client.tsで追加
  • componentを使って表示させるためにcomponents/blog-parts.tsxに追加
  • 表示させたい場所pages/blog/index.tsxにcomponentを追加

export interface Post {
  PageId: string;
  Title: string;
  Excerpt: string;
  Date: string;
  Slug: string;👉追加
}
読み込む列typeに合わせた型をlib/interfaces.tsに追加

//省略
function _buildPost(data) {
  const prop = data.properties;
  const post: Post = {
    PageId: data.id,
    Title: prop.Title.title[0].plain_text,
    Excerpt: prop.Excerpt.rich_text[0].plain_text,
    Date: prop.Date.date.start,
    Slug: prop.Slug.rich_text[0].plain_text  👉追加
  };
  return post;
  //postに収納
}
propsで使うpostにslugデータをlib/client.tsで追加

//省略
export const PostSlug = ({ post }) => <p>{post.Slug ? post.Slug : ""}</p>; 👉追加
componentを使って表示させるためにcomponents/blog-parts.tsxに追加

//省略
const RenderPosts = ({ posts = [] }) => {
  return (
    <div className={styles.container}>
      <div className={styles.mainContent}>
        <p>***main-content***</p>
        <h2>BlogList page</h2>
        <br />
        {""}
        <section>
          <div>
            {/* 変数postsを1つの塊ごと取り出していく */}
            {posts.map((post) => {
              return (
                <div key={post.Date}>
                  <PostDate post={post} />
                  <PostTitle post={post} />
                  <PostExcerpt post={post} />
                  <PostSlug post={post} />  👉追加
                </div>
              );
            })}
          </div>
        </section>
        <br />
      </div>
      <div className={styles.subContent}>
        <p>***sub-content***</p>
      </div>
    </div>
  );
};
export default RenderPosts;
表示させたい場所pages/blog/index.tsxにcomponentを追加

ここまでの結果

image block

https://codesandbox.io/embed/slug-read-syy3lk?fontsize=14&hidenavigation=1&theme=dark

slugを記事毎のURLとして割り振る

1つのファイルを使い回すDynamic Routesという実装をしていきます\(^o^)/

https://nextjs.org/docs/routing/dynamic-routes

image block

  • 記事毎のページを作れるようにpages/blogフォルダ内[slug].tsxを用意する
  • [slug].tsxにひな形を少し修正して追加

import { useRouter } from "next/router";
import styles from "../../styles/blog.module.css";

const RenderPost = (post) => {
  const router = useRouter();
  const { slug } = router.query;

  return (
    <div className={styles.container}>
      <div className={styles.mainContent}>
        <p>Post: {slug}</p>
      </div>
    </div>
  );
};

export default RenderPost;
pages/blog/[slug].tsx

こんな感じに表示したい.....

image block

そのためには......

🤔
slugの値毎に固有のページリンクが設置されるようにする必要がありそう........

やること1:propsを作る

https://nextjs.org/docs/basic-features/data-fetching/get-static-props

image block

NotionAPIから取得したデータをいざ使うぜっていうときには

getStaticPropsを本編(今回でいうとRenderPost)の前に記述します。

  • 指定したslugの値と一致したNotionデータベースの行を取得するためにclient.tsgetPostBySlugを追加
  • 記事毎のページはgitPostBySlugのデータを使うようにpages/blog/[slug].tsxgetStaticPropsに追加

//省略
export async function getPostBySlug(slug: string) {
  const data = await client.databases.query({
    database_id: DATABASE_ID,
    filter: {
      property: "Slug",
      rich_text: {
        equals: slug
      }
    }
  });
  return _buildPost(data.results[0]);
}
指定したslugの値と一致したNotionデータベースの行を取得するためにclient.tsにgetPostBySlugを追加

import { useRouter } from "next/router";
import { getPostBySlug } from "../../lib/client";
import styles from "../../styles/blog.module.css";

export async function getStaticProps({ params: { slug } }) {
  const post = await getPostBySlug(slug);
  return {
    props: { post },
    revalidate: 60
  };
}


const RenderPost = (post) => {
//省略
記事毎のページはgitPostBySlugのデータを使うようにpages/blog/[slug].tsxのgetStaticPropsに追加

やること2:pathを作る

https://nextjs.org/docs/basic-features/data-fetching/get-static-paths

image block
Dynamic RoutesのpageでgetStaticPropsを使うなら、pathの一覧をを定義するgetStaticPathsは必要。

  • 必要な場面で必要なNotionデータベースの行を取り出せるようにclient.tsgetAllPostsを追加
  • slugの値が記事毎のURLになるようlib/blog-helpers.tsに追加
  • getAllPostsから記事毎のリンクを生成できるようにpages/blog/[slug].tsxgetStaticPathsを追加

export async function getAllPosts() {
  let results = [];
  const params = {
    database_id: DATABASE_ID,
    page_size: 100
  };

  const data = await client.databases.query(params);
  results = results.concat(data.results);
  params["start_cursor"] = data.next_cursor;

  return results.map((item) => _buildPost(item));
}
必要な場面で必要なNotionデータベースの行を取り出せるようにclient.tsにgetAllPostsを追加

😵
記事一覧ページで使ったgetPostsと何が違うの??
getPostsとgetAllPostsの違い

export async function getPosts() {
  const params = {
    database_id: DATABASE_ID
  };

  const data = await client.databases.query(params);

  return data.results.map((item) => _buildPost(item));
}
pages/blog/index.tsxで使うためにlib/clients.tsへ以前記述したgetPosts
export async function getAllPosts() {
  let results = [];
  const params = {
    database_id: DATABASE_ID,
    page_size: 100
  };

  const data = await client.databases.query(params);
  results = results.concat(data.results);
  params["start_cursor"] = data.next_cursor;

  return results.map((item) => _buildPost(item));
}
getAllPosts

🤔
getAllPostsではresultsという変数にdata.resultsを収納してる.....

image block

https://developers.notion.com/reference/property-item-object#paginated-property-values

だからなに?って感じなのでよくよく調べてみると.....

こんなリファレンスを発見しました🤦‍♀️

image block

https://developers.notion.com/reference/pagination

objectの一覧を返すエンドポイントでは、Paginationが使われる。
Pagenationにより、APIはリストの一部をリクエストし、結果の配列(array)とnext_cursorをレスポンスで受け取ることが可能になる。
APIは、他のリクエストでリストの次の部分を受け取るためにnext_cursor を使用できる。
このテクニックを使って、APIはリスト全体 (または必要な部分だけ) を受け取るようにリクエストし続けることが可能。

※ リクエスト・リスポンスという表現が分かりにくい方はBlog learn02を参照ください

🤔
next_cursorってparamsの部分で後から追加していたな......

もういちどgetAllPostsを確認....

export async function getAllPosts() {
  let results = [];
  const params = {
    database_id: DATABASE_ID,
    page_size: 100 
  };

  const data = await client.databases.query(params);
  results = results.concat(data.results);
  params["start_cursor"] = data.next_cursor; 👉ここ

  return results.map((item) => _buildPost(item));
}
getAllPosts

Requestsにはstart_cursorとpage_sizeがある。

image block
NorionAPI公式リファレンス Pagination内
start_cursorはデフォルトでは一番目のデータを示す。
次のページのrequestで使うとき、直前のresponseから返ってきたcorsorになる。
🤔
start_cursornext_cursorを代入してたよな......next_cursorってどこにあるんだ???

Responsesにnext_cursorを発見!!!

ついでにさっき「だからなに?」ってなってしまったresultsもある!!!

image block
NotionAPI公式リファレンス Pagenation内

▼ next_cursorについて.....

同じエンドポイントに start_cursor パラメータとして値を渡すことで、次のページの結果を取得するために使用される。

start_cursorは直前の値。

next_cursorの値としてstart_cursorを更新してあげれば

次のデータがrequestされたことになる

\(^o^)/わんこそばみたい\(^o^)/

▼ resultsについて.....

Pagenationの出だしに説明されていた、

Pagenationにより、APIはリストの一部をリクエストし、結果の配列(array)next_cursorをレスポンスで受け取ることが可能になる。

って部分。

この配列ってresultsのことを意味している\(^o^)/

その上でもう一度getAllPostsを眺めてみる.....

export async function getAllPosts() {
  let results = [];
  const params = {
    database_id: DATABASE_ID,
    page_size: 100 
  };

  const data = await client.databases.query(params);
  results = results.concat(data.results);
  params["start_cursor"] = data.next_cursor; 👉ここ

  return results.map((item) => _buildPost(item));
}
getAllPosts

いい景色だ.....(*´ω`*)(*´ω`*)💚

まだ2つやってなかったこと
  • slugの値が記事毎のURLになるようlib/blog-helpers.tsに追加
  • getAllPostsから記事毎のリンクを生成できるようにpages/blog/[slug].tsxgetStaticPathsを追加

ブラケットで囲ってパスを記述します。

image block

https://nextjs.org/docs/routing/introduction#dynamic-route-segments

//省略
export const getBlogLink = (slug: string) => {
  return `/blog/${slug}`;
};
slugの値が記事毎のURLになるようlib/blog-helpers.tsに追加

import { getPostBySlug, getAllPosts } from "../../lib/client";   //getAllPosts追加
import { getBlogLink } from "../../lib/blog-helpers";
//他のimportは同じなので省略

//同じなので省略 export async function getStaticProps

export async function getStaticPaths() {
  const posts = await getAllPosts();
  return {
    paths: posts.map((post) => getBlogLink(post.Slug)),
    fallback: "blocking"
  };
}
//同じなので省略 const RenderPost
getAllPostsから記事毎のリンクを生成できるようにpages/blog/[slug].tsxのgetStaticPathsを追加

ここまでの結果

image block

すると表示される\(^o^)/

image block

https://codesandbox.io/embed/slug-read-router-9e1fbm?fontsize=14&hidenavigation=1&theme=dark

割り振ったURLを記事タイトルに埋め込む

記事一覧ページに表示されるタイトルにリンクを埋め込み、

手打ちをしなくても記事固有のページへ移動できるようにしてみます(*´∀`*)

やること
  • 記事タイトルにリンクを埋め込むためのcomponentをcomponents/blog-parts.tsxに追加

import Link from "next/link";
import { getDateStr, getBlogLink } from "../lib/blog-helpers"; // getBlogLinkを追加
//他のimportは同じなので省略

export const PostTitle = ({ post }) => {
  const postTitle = post.Title ? post.Title : "";
  return (
    <h3>
      <Link href="/blog/[slug]" as={getBlogLink(post.Slug)} passHref>
        <a>{postTitle}</a>
      </Link>
    </h3>
  );
};
//他の export constは同じなので省略
記事タイトルにリンクを埋め込むためのcomponentをcomponents/blog-parts.tsxに追加

😅
getBlogLinkってなんだっけ....???

slugをリンクに追加されるように設定したこれ👇

//省略
export const getBlogLink = (slug: string) => {
  return `/blog/${slug}`;
};
slugの値が記事毎のURLになるようlib/blog-helpers.tsに追加

Next.jsのLinkについて

image block

https://nextjs.org/docs/api-reference/next/link

image block
Next.js 公式ドキュメント/API Reference/next/linkより

href:pathかURL
as:ブラウザ上のURLに表示されるpath
passHref:hrefを子要素にする
🤔
子要素ってas(ブラウザ上のURLのpath)を意味しているのかな....????

もう一度該当するコードを確認すると.....

      <Link href="/blog/[slug]" as={getBlogLink(post.Slug)} passHref>
        <a>{postTitle}</a>
      </Link>
components/blog-parts.tsxの一部
  • href:/blog/[slug] というパス
  • as:getBlogLink…… なんだっけ???
//省略
export const getBlogLink = (slug: string) => {
  return `/blog/${slug}`;
};
lib/blog-helpers.tsの一部
  • as:/blog/slug列の値としたブラウザ上URLのpath

slugの値が【notion-api_make-blog】の場合、

/blog/notion-api_make-blog

となる記述だ\(^o^)/

ここまでの結果

image block

リンクの埋め込まれている記事タイトルをクリックすると.......

image block

https://codesandbox.io/embed/slug-read-router-page-shi-zhuang-nltin7?fontsize=14&hidenavigation=1&theme=dark

表示された\(^o^)/

今度は手打ちしないでOK〜〜〜〜〜

まとめ

  1. NotionAPIを使ってslugを表示させる
    1. 読み込む列typeに合わせた型をlib/interfaces.tsに追加
    2. propsで使うpostにslugデータをlib/client.tsで追加
    3. componentを使って表示させるためにcomponents/blog-parts.tsxに追加
    4. 表示させたい場所pages/blog/index.tsxにcomponentを追加
  2. slugを記事毎のURLとして割り振る
    1. 記事毎のページを作れるようにpages/blogフォルダ内[slug].tsxを用意する
    2. [slug].tsxにひな形を少し修正して追加
    3. propsを作る
      1. 指定したslugの値と一致したNotionデータベースの行を取得するためにclient.tsgetPostBySlugを追加
      2. 記事毎のページはgitPostBySlugのデータを使うようにpages/blog/[slug].tsxgetStuticPropsに追加
    4. pathを作る
      1. 必要な場面で必要なNotionデータベースの行を取り出せるようにclient.tsgetAllPostsを追加
        1. getPostsとgetAllPostsの違い
      2. slugの値が記事毎のURLになるようlib/blog-helpers.tsに追加
      3. getAllPostsから記事毎のリンクを生成できるようにpages/blog/[slug].tsxgetStuticPathsを追加

  3. 割り振ったURLを記事タイトルに埋め込む
    1. 記事タイトルにリンクを埋め込むためのcomponentをcomponents/blog-parts.tsxに追加
      1. Next.jsのLinkについて

今回PRして追加したのは#12です\(^o^)/

エラーがあり、後日修正した分は#13にあります。

No.branchNamedescriptionリンクPR
10oek04hslugを使って画面遷移_getStaticPathhttps://github.com/herohoro/Blog_learn/pull/12#12
#13
11次回\(^o^)/記事固有ページにDBのタイトル・日付を表示させる_useEffect

https://github.com/herohoro/Blog_learn/wiki

次回はNotionデータベースのpage内データを取得してブログの記事として表示する流れを解説していきます\(^o^)/

調べれば調べるほどNotionAPIリファレンス見方が分かってきて、

見れば見るほど「んぐぁ~」となるeasy-notion-blogリボジトリ。

APIに初めて触れる人間にとって

面白くてたまらない体験でした。

取得データは同じでも、

見せ方によって七変化なところが面白い。

APIってすごいやー。

とリビングの片隅で余韻に浸るへろほろなのでした。。。。。(*´∀`*)


Xではたま〜にする更新のお知らせを行っています

興味ある方はLet'sフォロー★

▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね

Buy Me A Coffee

新着記事を通知したい??


RSSリーダーにatomのリンクを登録すると通知が行くよ🐌

https://herohoro.com/atom

やってみてね(*´ω`*)(*´ω`*)

Twitter Timeline


フォロー大歓迎\(^o^)/