Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ export const FORM_URL =
"https://docs.google.com/forms/d/e/1FAIpQLSc1-E-aEEFeRlxggQLTea6SY96W-1fNVUN5MxVYKWfmId1yUg/viewform";
export const JOIN_URL =
"https://docs.google.com/forms/d/e/1FAIpQLSdEsHCLaclQtPJf7it7b-N6OJIV9nXOwgEJA6Pl_hp6nYOeiA/viewform?usp=header";

// ISR(Incremental Static Regeneration)の再生成間隔(秒)。
// ビルド時に全ページを生成するのではなく、リクエスト時に生成してこの間隔でキャッシュを更新することで、
// ビルドごとのNotion APIの呼び出し回数を大幅に削減する。
// 1時間 = Notionの画像URLの有効期限ともおおよそ揃えている。
export const ISR_REVALIDATE_SECONDS = 3600;
2 changes: 2 additions & 0 deletions src/pages/achievements/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { AchievementsScreen } from "@/screens/Achievements";
import { Props as AchievementItemProps } from "@/ui/AchievementItem";
import cacheRemoteImage from "@/utils/cacheRemoteImage";
Expand Down Expand Up @@ -62,5 +63,6 @@ export const getStaticProps = async () => {
props: {
achievements,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
};
35 changes: 22 additions & 13 deletions src/pages/blog/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { BlogArticleScreen } from "@/screens/BlogArticle";
import { Block, Column } from "@/types/block";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Props as PageInfo } from "@/ui/ArticleTitle";
import cacheRemoteImage from "@/utils/cacheRemoteImage";
import createOGPImage from "@/utils/createOGPImage";
import { Meta } from "@/utils/meta";
import { getBlocks, getDatabase, getPage } from "@/utils/notion";
import { getBlocks, getPage } from "@/utils/notion";
import { getArticles } from "@/utils/useGetArticles";

export default function Article({
Expand Down Expand Up @@ -42,17 +43,24 @@ export default function Article({
}

export const getStaticPaths = async () => {
const articles = await getArticles();

const paths = articles.map((article) => ({
params: { id: article.id },
}));

return { paths, fallback: false };
// ビルド時に全記事を事前生成すると記事数に比例してNotion APIを大量に呼び出してしまう。
// パスは事前生成せず、リクエスト時にオンデマンド生成し、ISRでキャッシュする。
return { paths: [], fallback: "blocking" };
};

export const getStaticProps = async ({ params }: { params: { id: string } }) => {
const pageId = params.id as string;

// 公開記事の一覧。記事の存在確認とおすすめ記事の両方に使い回し、Notionへの問い合わせを1回に抑える。
const publicArticles = await getArticles();

// fallback: 'blocking' では任意のIDでアクセスされ得るため、
// 公開記事に含まれないID(非公開・存在しない・公開日前)は404にする。
const isPublicArticle = publicArticles.some((article) => article.id === pageId);
if (!isPublicArticle) {
return { notFound: true, revalidate: ISR_REVALIDATE_SECONDS };
}

const blocks = (await getBlocks(pageId)) as Block[];
const page = (await getPage(pageId)) as any;
const createdBy = page.properties.Created_By?.formula?.string;
Expand Down Expand Up @@ -128,9 +136,7 @@ export const getStaticProps = async ({ params }: { params: { id: string } }) =>
.join("");
const description = fullText.length <= 100 ? fullText : fullText.slice(0, 100) + "...";

const getSuggestArticles = async () => {
const publicArticles = await getArticles();

const getSuggestArticles = () => {
const shuffleArray = (array: any[]) => {
for (let i = array.length - 1; i >= 0; i--) {
const tmp = Math.floor(Math.random() * (i + 1));
Expand All @@ -140,12 +146,14 @@ export const getStaticProps = async ({ params }: { params: { id: string } }) =>
};

const suggestLength = 3;
const results = shuffleArray(publicArticles).slice(0, suggestLength);
// 表示中の記事を除いた公開記事からランダムに選ぶ(publicArticlesを再利用)
const candidates = publicArticles.filter((article) => article.id !== pageId);
const results = shuffleArray(candidates).slice(0, suggestLength);

return results as ArticleItemProps[];
};

const suggestArticles = await getSuggestArticles();
const suggestArticles = getSuggestArticles();

const title = page.properties.Name.title[0].plain_text;
const writerName = customName || createdBy || null;
Expand All @@ -160,5 +168,6 @@ export const getStaticProps = async ({ params }: { params: { id: string } }) =>
description: description,
lastEditedTime: page.last_edited_time,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
};
2 changes: 2 additions & 0 deletions src/pages/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { BlogScreen } from "@/screens/Blog";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Meta } from "@/utils/meta";
Expand All @@ -19,5 +20,6 @@ export async function getStaticProps() {
props: {
articles,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
}
84 changes: 39 additions & 45 deletions src/pages/blog/tag/[tag]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
import { BlogScreen } from "@/screens/Blog";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Meta } from "@/utils/meta";
import { getDatabase } from "@/utils/notion";
import { getArticles } from "@/utils/useGetArticles";

export default function TagPage({ tag, articles }: { tag: string; articles: ArticleItemProps[] }) {
const headingText = `${tag}に関する記事`;
return (
<>
<Meta title={headingText} />
<BlogScreen articles={articles} headingText={headingText} />
</>
);
}

export async function getStaticPaths() {
const databaseId = process.env.NOTION_BLOG_DATABASE_ID;
const articleDb = await getDatabase(databaseId);

const tags = new Set<string>();
articleDb.forEach((article: any) => {
article.properties.tag.multi_select.forEach((tag: any) => {
tags.add(tag.name);
});
});

const paths = Array.from(tags).map((tag) => ({
params: { tag: tag },
}));

return { paths, fallback: false };
}

export async function getStaticProps({ params }: { params: { tag: string } }) {
const tag = params.tag;
const articles = await getArticles(tag);

return {
props: {
tag,
articles,
},
};
}
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { BlogScreen } from "@/screens/Blog";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Meta } from "@/utils/meta";
import { getArticles } from "@/utils/useGetArticles";

export default function TagPage({ tag, articles }: { tag: string; articles: ArticleItemProps[] }) {
const headingText = `${tag}に関する記事`;
return (
<>
<Meta title={headingText} />
<BlogScreen articles={articles} headingText={headingText} />
</>
);
}

export async function getStaticPaths() {
// タグの一覧取得のためだけにビルド時へNotionへ問い合わせるのを避け、
// リクエスト時にオンデマンド生成してISRでキャッシュする。
return { paths: [], fallback: "blocking" };
}

export async function getStaticProps({ params }: { params: { tag: string } }) {
const tag = params.tag;
const articles = await getArticles(tag);

// 公開記事が存在しないタグ(存在しないタグへの直接アクセスなど)は404にする。
if (articles.length === 0) {
return { notFound: true, revalidate: ISR_REVALIDATE_SECONDS };
}

return {
props: {
tag,
articles,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
}
100 changes: 46 additions & 54 deletions src/pages/blog/writer/[writer]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,46 @@
import { BlogScreen } from "@/screens/Blog";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Meta } from "@/utils/meta";
import { getDatabase } from "@/utils/notion";
import { getArticles } from "@/utils/useGetArticles";

export default function WriterPage({
writer,
articles,
}: {
writer: string;
articles: ArticleItemProps[];
}) {
const headingText = `${writer}による記事`;

return (
<>
<Meta title={headingText} />
<BlogScreen articles={articles} headingText={headingText} />
</>
);
}

export async function getStaticPaths() {
const databaseId = process.env.NOTION_BLOG_DATABASE_ID;
const articleDb = await getDatabase(databaseId);

const writers = new Set<string>();
articleDb.forEach((article: any) => {
const customName = article.properties.Custom_Name?.rich_text?.[0]?.plain_text;
const createdBy = article.properties.Created_By?.formula?.string;
const writerName = customName || createdBy;
if (writerName) {
writers.add(writerName);
}
});
const paths = Array.from(writers).map((writer) => ({
params: { writer: writer },
}));

return { paths, fallback: false };
}

export async function getStaticProps({ params }: { params: { writer: string } }) {
const writer = params.writer;
const articles = await getArticles(undefined, params.writer);

return {
props: {
writer,
articles,
},
};
}
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { BlogScreen } from "@/screens/Blog";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { Meta } from "@/utils/meta";
import { getArticles } from "@/utils/useGetArticles";

export default function WriterPage({
writer,
articles,
}: {
writer: string;
articles: ArticleItemProps[];
}) {
const headingText = `${writer}による記事`;

return (
<>
<Meta title={headingText} />
<BlogScreen articles={articles} headingText={headingText} />
</>
);
}

export async function getStaticPaths() {
// ライター一覧の取得のためだけにビルド時にNotionへ問い合わせるのを避け、
// リクエスト時にオンデマンド生成してISRでキャッシュする。
return { paths: [], fallback: "blocking" };
}

export async function getStaticProps({ params }: { params: { writer: string } }) {
const writer = params.writer;
const articles = await getArticles(undefined, params.writer);

// 公開記事が存在しないライター(存在しないライターへの直接アクセスなど)は404にする。
if (articles.length === 0) {
return { notFound: true, revalidate: ISR_REVALIDATE_SECONDS };
}

return {
props: {
writer,
articles,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
}
2 changes: 2 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { TopScreen } from "@/screens/Top";
import { Props as ArticleItemProps } from "@/ui/ArticleItem";
import { ProductionDetailProps as ProductionProps } from "@/ui/Production";
Expand Down Expand Up @@ -147,5 +148,6 @@ export const getStaticProps = async () => {
product: filteredProduct,
articles: filteredArticles,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
};
2 changes: 2 additions & 0 deletions src/pages/products/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISR_REVALIDATE_SECONDS } from "@/constants";
import { ProductsScreen } from "@/screens/Products";
import { ProductionDetailProps as ProductionProps } from "@/ui/Production";
import cacheRemoteImage from "@/utils/cacheRemoteImage";
Expand Down Expand Up @@ -73,5 +74,6 @@ export const getStaticProps = async () => {
props: {
products,
},
revalidate: ISR_REVALIDATE_SECONDS,
};
};
Loading