Skip to content

Commit

Permalink
feat: add sitemap and robots.txt
Browse files Browse the repository at this point in the history
  • Loading branch information
Kohei Asai committed Feb 4, 2024
1 parent 4f615c5 commit 23182f2
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 6 deletions.
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@wooorm/starry-night": "^3.2.0",
"cheerio": "^1.0.0-rc.12",
"clsx": "^2.1.0",
"date-fns": "^3.3.1",
"hasha": "^6.0.0",
"mdast-util-to-string": "^4.0.0",
"negotiator": "^0.6.3",
Expand Down
16 changes: 16 additions & 0 deletions src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type MetadataRoute } from "next";
import { getConfig } from "~/helpers/config";

function robots(): MetadataRoute.Robots {
const config = getConfig();

return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: `${config.urlOrigin}/sitemap.xml`,
};
}

export default robots;
30 changes: 30 additions & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type MetadataRoute } from "next";
import { getConfig } from "~/helpers/config";
import { queryAllPostsRegardlessLocale } from "~/queries/query-all-posts-regardless-locale";

async function sitemap(): Promise<MetadataRoute.Sitemap> {
const config = getConfig();
const posts = await queryAllPostsRegardlessLocale();

const entries: MetadataRoute.Sitemap = [
{
url: `${config.urlOrigin}/`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 1,
},
];

for (const post of posts) {
entries.push({
url: `${config.urlOrigin}/posts/${post.slug}`,
lastModified: post.lastEditedAt,
changeFrequency: "weekly",
priority: 0.9,
});
}

return entries;
}

export default sitemap;
12 changes: 10 additions & 2 deletions src/helpers/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
interface Config {
urlOrigin: string;
notion: {
integrationSecret: string;
bioDatabaseId: string;
Expand All @@ -24,17 +25,24 @@ function resolveEnvironmentVariable(key: string): string {
}

export function getConfig(): Config {
let urlOrigin = `http://localhost:${process.env.PORT ?? "3000"}`;

if (process.env.VERCEL_URL !== undefined) {
urlOrigin = `https://${process.env.VERCEL_URL}`;
}

return {
urlOrigin,
notion: {
integrationSecret: resolveEnvironmentVariable(
"NOTION_INTEGRATION_SECRET",
"NOTION_INTEGRATION_SECRET"
),
bioDatabaseId: resolveEnvironmentVariable("NOTION_BIO_DATABASE_ID"),
postDatabaseId: resolveEnvironmentVariable("NOTION_POST_DATABASE_ID"),
},
googleAnalytics: {
measurementId: resolveEnvironmentVariable(
"GOOGLE_ANALYTICS_MEASUREMENT_ID",
"GOOGLE_ANALYTICS_MEASUREMENT_ID"
),
},
image: {
Expand Down
13 changes: 10 additions & 3 deletions src/helpers/notion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,17 @@ const zNotionCreatedByProperty = z.object({
id: z.string().min(1),
name: z.string(),
avatar_url: z.string().url(),
person: z.object({
email: z.string().email(),
}),
}),
});

const zNotionCreatedByPropertyDeserializable =
zNotionCreatedByProperty.transform<Author>((value) => {
return {
name: value.created_by.name,
email: value.created_by.person.email,
avatarImageUrl: new URL(value.created_by.avatar_url),
};
});
Expand Down Expand Up @@ -216,10 +220,11 @@ const zPostNotionPage = zNotionPage.omit({ properties: true }).extend({
cover: zNotionFile,
properties: z.object({
Title: zNotionTitleProperty,
Summary: zNotionRichTextProperty,
Slug: zNotionRichTextProperty,
Status: zNotionStatusProperty,
Locale: zNotionSelectProperty,
Tags: zNotionMultiSelectPropertyDeserialized,
Keywords: zNotionMultiSelectPropertyDeserialized,
"Created at": zNotionCreatedTimeProperty,
"Created by": zNotionCreatedByProperty,
"Last edited at": zNotionLastEditedTimeProperty,
Expand All @@ -232,10 +237,11 @@ const zPostNotionPageDeserialized = zPostNotionPage
cover: zNotionFileDeserialized,
properties: z.object({
Title: zNotionTitlePropertyDeserialized,
Summary: zNotionRichTextPropertyDeserialized,
Slug: zNotionRichTextPropertyDeserialized,
Status: zNotionStatusPropertyDeserialized,
Locale: zNotionSelectPropertyDeserialized,
Tags: zNotionMultiSelectPropertyDeserialized,
Keywords: zNotionMultiSelectPropertyDeserialized,
"Created at": zNotionCreatedTimePropertyDeserialized,
"Created by": zNotionCreatedByPropertyDeserializable,
"Last edited at": zNotionLastEditedTimePropertyDeserialized,
Expand All @@ -248,7 +254,8 @@ const zPostNotionPageDeserialized = zPostNotionPage
slug: value.properties.Slug,
locale: value.properties.Locale as Locale,
title: value.properties.Title,
tags: value.properties.Tags,
summary: value.properties.Summary,
keywords: value.properties.Keywords,
coverImageUrl: new URL(value.cover),
createdAt: value.properties["Created at"],
createdBy: value.properties["Created by"],
Expand Down
4 changes: 3 additions & 1 deletion src/models/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type Locale } from "~/models/locale";

interface Author {
name: string;
email: string;
avatarImageUrl: URL;
}

Expand All @@ -10,7 +11,8 @@ interface Post {
slug: string;
locale: Locale;
title: string;
tags: string[];
summary: string;
keywords: string[];
coverImageUrl: URL;
createdAt: Date;
createdBy: Author;
Expand Down
39 changes: 39 additions & 0 deletions src/queries/query-all-posts-regardless-locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import "server-only";

import { isAfter } from "date-fns";
import { availableLocales } from "~/helpers/locale";
import { type Locale } from "~/models/locale";
import { type Post } from "~/models/post";
import { findPosts } from "~/repositories/find-posts";

async function queryAllPostsRegardlessLocale(): Promise<Post[]> {
const postsByLocale: Partial<Record<Locale, Post[]>> = {};

await Promise.all(
availableLocales.map(async (locale) => {
const posts = await findPosts({ locale, includeDrafts: false });

postsByLocale[locale] = posts;
}),
);

const postsBySlug: Record<Post["slug"], Post> = {};

for (const posts of Object.values(postsByLocale)) {
for (const post of posts) {
postsBySlug[post.slug] ??= post;

if (isAfter(post.createdAt, postsBySlug[post.slug].createdAt)) {
postsBySlug[post.slug].createdAt = post.createdAt;
}

if (isAfter(post.lastEditedAt, postsBySlug[post.slug].lastEditedAt)) {
postsBySlug[post.slug].lastEditedAt = post.lastEditedAt;
}
}
}

return Object.values(postsBySlug);
}

export { queryAllPostsRegardlessLocale };

0 comments on commit 23182f2

Please sign in to comment.