Skip to content
Merged
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
45 changes: 42 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,26 @@ import './globals.css'

const siteUrl = 'https://machigai-salad.llll-ll.com'

// Longer, keyword-bearing description for search snippets. The on-page subtitle
// stays short ('間違いさがし おたすけツール'); this is the search-facing copy.
const description =
'2枚の画像を並べて拡大・比較できる間違い探しのおたすけツール。答え合わせやヒント探しに便利。インストール不要、ブラウザだけで動きます。'

export const metadata: Metadata = {
metadataBase: new URL(siteUrl),
title: '小エビの間違いサラダ',
description: '間違いさがし おたすけツール',
description,
keywords: [
'間違い探し',
'間違いさがし',
'まちがいさがし',
'間違いサラダ',
'答え合わせ',
'ヒント',
'画像比較',
'拡大',
'ツール',
],
manifest: '/manifest.webmanifest',
appleWebApp: {
capable: true,
Expand All @@ -19,15 +36,15 @@ export const metadata: Metadata = {
type: 'website',
url: siteUrl,
title: '小エビの間違いサラダ',
description: '間違いさがし おたすけツール',
description,
siteName: '小エビの間違いサラダ',
locale: 'ja_JP',
images: [{ url: `${siteUrl}/static/ogp.webp`, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
title: '小エビの間違いサラダ',
description: '間違いさがし おたすけツール',
description,
images: [`${siteUrl}/static/ogp.webp`],
},
icons: {
Expand All @@ -36,6 +53,24 @@ export const metadata: Metadata = {
},
}

// Structured data so search engines can render the tool as a SoftwareApplication
// rich result. Free, browser-based, Japanese.
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: '小エビの間違いサラダ',
url: siteUrl,
description,
applicationCategory: 'UtilitiesApplication',
operatingSystem: 'Web',
inLanguage: 'ja',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'JPY',
},
}

export const viewport: Viewport = {
themeColor: '#FEF6DC',
}
Expand All @@ -48,6 +83,10 @@ export default function RootLayout({
return (
<html lang="ja">
<body className="antialiased">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<I18nProvider>
{children}
<ToastContainer />
Expand Down
18 changes: 18 additions & 0 deletions app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { MetadataRoute } from 'next'

// Canonical production deploy (Cloudflare). basePath is only used for the
// GitHub Pages mirror; the indexable site lives at the apex subdomain.
const siteUrl = 'https://machigai-salad.llll-ll.com'

export const dynamic = 'force-static'

export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: `${siteUrl}/sitemap.xml`,
host: siteUrl,
}
}
19 changes: 19 additions & 0 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { MetadataRoute } from 'next'

const siteUrl = 'https://machigai-salad.llll-ll.com'

export const dynamic = 'force-static'

// Single-page app: one canonical entry is enough. lastModified uses the build
// date (JST) already exposed via next.config so the sitemap stays in step with
// each deploy without manual editing.
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: siteUrl,
lastModified: process.env.BUILD_DATE,
changeFrequency: 'monthly',
priority: 1,
},
]
}
Loading