記事ページSEO対応#29
Conversation
- 記事ページをSSR化し、generateMetadataで動的メタ情報を生成 - title, description, OGP (og:title, og:description, og:type等) - twitter:card メタタグ - canonical URL - JSON-LD構造化データ(Articleスキーマ)を追加 - SSR側で記事コンテンツを非表示テキストとして埋め込み(クローラー対応) - robots.txt を追加(公開ページのみ許可、管理画面はdisallow) - sitemap.xml を動的生成(公開済み記事一覧を含む) - TipTapコンテンツからプレーンテキストを抽出するユーティリティを追加 Co-authored-by: HitoshiMatsuno <MatsunoHitoshi@users.noreply.github.com>
|
Cursor Agent can help with this pull request. Just |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary of ChangesHello @MatsunoHitoshi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! このプルリクエストは、記事ページのSEO(検索エンジン最適化)を大幅に改善することを目的としています。記事コンテンツが検索エンジンに適切にインデックスされ、より多くのユーザーに発見されるように、サーバーサイドレンダリングへの移行、動的なメタデータと構造化データの追加、そしてクローラー制御のための Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
このプルリクエストは、記事ページをサーバーコンポーネントに変換し、動的なメタデータ、JSON-LD、robots.txt、サイトマップを追加することで、SEOを包括的に改善するものです。コードは全体的にうまく構成されています。私のフィードバックは、データベースクエリのキャッシュによるパフォーマンス向上、コードの重複を減らして保守性を高めること、そしてサイトマップのSEO詳細を改善してクロールの効率を上げること、といった点に焦点を当てています。また、型定義に関する小さな改善提案も含まれています。
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export function extractTextFromTiptap(node: any, maxLength = 200): string { |
| async function getWorkspaceForMeta(workspaceId: string) { | ||
| const workspace = await db.workspace.findFirst({ | ||
| where: { | ||
| id: workspaceId, | ||
| status: WorkspaceStatus.PUBLISHED, | ||
| isDeleted: false, | ||
| }, | ||
| ); | ||
| select: { | ||
| id: true, | ||
| name: true, | ||
| description: true, | ||
| content: true, | ||
| createdAt: true, | ||
| updatedAt: true, | ||
| user: { | ||
| select: PUBLIC_USER_SELECT, | ||
| }, | ||
| tags: { | ||
| select: { name: true }, | ||
| }, | ||
| }, | ||
| }); | ||
| return workspace; | ||
| } |
There was a problem hiding this comment.
getWorkspaceForMeta 関数は generateMetadata と ArticlePage の両方から呼び出されています。react の cache 関数でこの関数をラップすることで、リクエストごとにデータベースへのクエリが一度だけ実行されるようになり、パフォーマンスが向上します。
また、ファイルの先頭に import { cache } from 'react'; を追加してください。
const getWorkspaceForMeta = cache(async (workspaceId: string) => {
const workspace = await db.workspace.findFirst({
where: {
id: workspaceId,
status: WorkspaceStatus.PUBLISHED,
isDeleted: false,
},
select: {
id: true,
name: true,
description: true,
content: true,
createdAt: true,
updatedAt: true,
user: {
select: PUBLIC_USER_SELECT,
},
tags: {
select: { name: true },
},
},
});
return workspace;
});
| const description = | ||
| workspace.description ?? | ||
| (extractTextFromTiptap(workspace.content, 160) || | ||
| `${workspace.name} – ArsTraverse で公開された記事`); |
There was a problem hiding this comment.
この description を生成するロジックは、90行目からの ArticleJsonLd コンポーネント内でも繰り返されています。コードの重複を避け、保守性を高めるために、このロジックをヘルパー関数に切り出すことをお勧めします。
例えば、以下のような関数を定義し、両方の場所から呼び出すことができます。
function getArticleDescription(workspace: NonNullable<Awaited<ReturnType<typeof getWorkspaceForMeta>>>): string {
return workspace.description ??
(extractTextFromTiptap(workspace.content, 160) ||
`${workspace.name} – ArsTraverse で公開された記事`);
}| const description = | |
| workspace.description ?? | |
| (extractTextFromTiptap(workspace.content, 160) || | |
| `${workspace.name} – ArsTraverse で公開された記事`); | |
| const description = getArticleDescription(workspace); |
| ], | ||
| }, | ||
| ], | ||
| const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "https://arstraverse.com"; |
There was a problem hiding this comment.
baseUrl の定義が複数のファイルで重複しています。これを共通の定数として例えば src/lib/constants.ts のようなファイルに切り出すことで、保守性が向上します。
重複箇所:
src/app/articles/[workspaceId]/page.tsx(この行と128行目)src/app/robots.ts(4行目)src/app/sitemap.ts(6行目)
提案:
src/lib/constants.ts:
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? "https://arstraverse.com";そして、各ファイルでは import { BASE_URL } from '@/lib/constants'; のようにインポートして const baseUrl = BASE_URL; として使用します。
| const description = | ||
| workspace.description ?? | ||
| (extractTextFromTiptap(workspace.content, 160) || | ||
| `${workspace.name} – ArsTraverse で公開された記事`); |
There was a problem hiding this comment.
先ほどの提案と同様に、こちらの description 生成ロジックも共通のヘルパー関数 getArticleDescription を使用するようにリファクタリングしてください。
| const description = | |
| workspace.description ?? | |
| (extractTextFromTiptap(workspace.content, 160) || | |
| `${workspace.name} – ArsTraverse で公開された記事`); | |
| const description = getArticleDescription(workspace); |
| const staticEntries: MetadataRoute.Sitemap = [ | ||
| { | ||
| url: baseUrl, | ||
| lastModified: new Date(), |
There was a problem hiding this comment.
lastModified: new Date() を使用すると、ビルドのたびにサイトマップの更新日時が新しくなり、実際にはコンテンツが変更されていなくても検索エンジンに更新されたと伝えてしまいます。これはクロールの非効率につながる可能性があります。ホームページのような静的なページについては、最後に大きな変更があった日付を固定で指定するか、頻繁に更新されない場合は lastModified を省略することを検討してください。
| lastModified: new Date(), | |
| lastModified: new Date("2024-01-01"), // 例: 最後に意味のある変更があった日付 |
| }, | ||
| { | ||
| url: `${baseUrl}/about`, | ||
| lastModified: new Date(), |
- Workspaceモデルに isSearchable フィールドを追加(デフォルト: false) - publish APIに isSearchable パラメータを追加 - updateSearchable APIを新設(公開済み記事の設定変更用) - 公開モーダルにトグルUIを追加 - 新規公開時: 検索エンジン公開を選択してから公開 - 公開済み: トグル変更で即座にAPIで保存 - generateMetadata で isSearchable=false の場合は noindex/nofollow を設定 - sitemap.xml は isSearchable=true の記事のみ含める - JSON-LD / SEOテキストも isSearchable=true の場合のみ出力 Co-authored-by: HitoshiMatsuno <MatsunoHitoshi@users.noreply.github.com>
Co-authored-by: HitoshiMatsuno <MatsunoHitoshi@users.noreply.github.com>
Implement comprehensive SEO for article pages to make them discoverable by search engines.
Previously, article pages were fully client-side rendered, making their content invisible to search engine crawlers. This PR converts the article pages to server components, adds dynamic metadata (title, description, OGP), structured data (JSON-LD), a
robots.txtfile, and a dynamicsitemap.xmlto ensure content is indexed and displayed correctly in search results.