Skip to content

Commit 00f8e55

Browse files
authored
Merge pull request #328 from yn1323/develop
main
2 parents f64c973 + a39c0f5 commit 00f8e55

File tree

16 files changed

+166
-17
lines changed

16 files changed

+166
-17
lines changed

index.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@
6161
}
6262
}
6363
</script>
64+
<script type="application/ld+json">
65+
{
66+
"@context": "https://schema.org",
67+
"@type": "Organization",
68+
"name": "シフトリ",
69+
"url": "https://shiftori.app",
70+
"logo": "https://shiftori.app/logo512.png"
71+
}
72+
</script>
73+
<script type="application/ld+json">
74+
{
75+
"@context": "https://schema.org",
76+
"@type": "WebSite",
77+
"name": "シフトリ",
78+
"url": "https://shiftori.app",
79+
"inLanguage": "ja-JP"
80+
}
81+
</script>
6482
</head>
6583
<body>
6684
<div id="app"></div>

public/robots.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
User-agent: *
22
Disallow: /dashboard
33
Disallow: /shiftboard
4+
Disallow: /shifts
5+
Disallow: /welcome
46
Allow: /
57

68
Sitemap: https://shiftori.app/sitemap.xml

public/sitemap.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
33
<url>
44
<loc>https://shiftori.app</loc>
5+
<lastmod>2026-04-16</lastmod>
56
<changefreq>monthly</changefreq>
67
<priority>1.0</priority>
78
</url>
89
<url>
910
<loc>https://shiftori.app/privacy</loc>
11+
<lastmod>2026-04-16</lastmod>
1012
<changefreq>yearly</changefreq>
1113
<priority>0.3</priority>
1214
</url>
1315
<url>
1416
<loc>https://shiftori.app/terms</loc>
17+
<lastmod>2026-04-16</lastmod>
1518
<changefreq>yearly</changefreq>
1619
<priority>0.3</priority>
1720
</url>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export type Faq = {
2+
q: string;
3+
a: string;
4+
};
5+
6+
export const faqs: Faq[] = [
7+
{
8+
q: "料金はかかりますか?",
9+
a: "無料です",
10+
},
11+
{
12+
q: "スタッフがメールアドレスを持っていない場合は?",
13+
a: "現在はメールアドレスが必須です 将来的にほかの方法にも対応予定です",
14+
},
15+
];

src/components/features/LandingPage/index.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Link as RouterLink } from "@tanstack/react-router";
44
import type { ReactNode } from "react";
55
import type { IconType } from "react-icons";
66
import { LuCalendarCheck, LuLink, LuSend } from "react-icons/lu";
7+
import { type Faq, faqs } from "./faqs";
78

89
export const LandingPage = () => {
910
return (
@@ -160,22 +161,6 @@ const PointCard = ({ icon: Icon, title, body }: Point) => (
160161
</VStack>
161162
);
162163

163-
type Faq = {
164-
q: string;
165-
a: string;
166-
};
167-
168-
const faqs: Faq[] = [
169-
{
170-
q: "料金はかかりますか?",
171-
a: "無料です",
172-
},
173-
{
174-
q: "スタッフがメールアドレスを持っていない場合は?",
175-
a: "現在はメールアドレスが必須です 将来的にほかの方法にも対応予定です",
176-
},
177-
];
178-
179164
const FaqSection = () => (
180165
<Box bg="gray.50" px={{ base: 4, lg: 12 }} py={{ base: 12, lg: 24 }}>
181166
<VStack mx="auto" w="full" maxW="768px" gap={{ base: 6, lg: 8 }} align="stretch">

src/helpers/seo/index.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* SEO helpers for TanStack Router's `head` option.
3+
*
4+
* TanStack Router's public `meta` type is typed as HTML `<meta>` attributes,
5+
* but its runtime also recognizes `{ title }` (renders `<title>`) and
6+
* `{ "script:ld+json": payload }` (renders a JSON-LD `<script>`). The entries
7+
* here produce runtime-valid tags; the final array is cast so the `head`
8+
* option accepts it.
9+
* @link https://tanstack.com/router/latest/docs/framework/react/guide/document-head-management
10+
*/
11+
import type { JSX } from "react";
12+
13+
const SITE_NAME = "シフトリ";
14+
const SITE_URL = "https://shiftori.app";
15+
16+
type MetaList = NonNullable<JSX.IntrinsicElements["meta"]>[];
17+
18+
type MetaEntry =
19+
| { title: string }
20+
| { name: string; content: string }
21+
| { property: string; content: string }
22+
| { "script:ld+json": Record<string, unknown> };
23+
24+
type BuildMetaOptions = {
25+
title: string;
26+
description?: string;
27+
/** Set true for pages that should not be indexed (magic-link pages, authed pages). */
28+
noindex?: boolean;
29+
/** Canonical path (e.g. "/terms"). Adds `og:url`. */
30+
canonical?: string;
31+
};
32+
33+
/**
34+
* Build route meta tags for TanStack Router's `head` option.
35+
* - Appends the site name to the title (except when the title is the site name itself)
36+
* - Mirrors description to `og:description`
37+
* - Adds `robots: noindex, nofollow` when `noindex` is set
38+
*/
39+
export const buildMeta = ({ title, description, noindex, canonical }: BuildMetaOptions): MetaList => {
40+
const fullTitle = title === SITE_NAME ? title : `${title} | ${SITE_NAME}`;
41+
const entries: MetaEntry[] = [{ title: fullTitle }, { property: "og:title", content: fullTitle }];
42+
43+
if (description) {
44+
entries.push({ name: "description", content: description });
45+
entries.push({ property: "og:description", content: description });
46+
}
47+
48+
if (canonical) {
49+
entries.push({ property: "og:url", content: `${SITE_URL}${canonical}` });
50+
}
51+
52+
if (noindex) {
53+
entries.push({ name: "robots", content: "noindex, nofollow" });
54+
}
55+
56+
return entries as unknown as MetaList;
57+
};
58+
59+
/**
60+
* Wrap a JSON-LD payload as a `head.meta` entry. Rendered as
61+
* `<script type="application/ld+json">...</script>` by TanStack Router.
62+
*/
63+
export const jsonLdMeta = (payload: Record<string, unknown>): MetaList =>
64+
[{ "script:ld+json": payload }] as unknown as MetaList;

src/routes/__root.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createRootRoute, Outlet, useRouterState } from "@tanstack/react-router";
1+
import { createRootRoute, HeadContent, Outlet, useRouterState } from "@tanstack/react-router";
22
import { useEffect } from "react";
33
import { Toaster } from "@/src/components/ui/toaster";
44
import { sendPageView } from "@/src/helpers/gtm";
@@ -16,6 +16,7 @@ const PageViewTracker = () => {
1616
export const Route = createRootRoute({
1717
component: () => (
1818
<>
19+
<HeadContent />
1920
<PageViewTracker />
2021
<Outlet />
2122
<Toaster />

src/routes/_auth/dashboard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import { api } from "@/convex/_generated/api";
55
import { DashboardContent } from "@/src/components/features/Dashboard/DashboardContent";
66
import { Animation } from "@/src/components/templates/Animation";
77
import { RootContentWrapper } from "@/src/components/templates/RootContentWrapper";
8+
import { buildMeta } from "@/src/helpers/seo";
89

910
export const Route = createFileRoute("/_auth/dashboard")({
11+
head: () => ({
12+
meta: buildMeta({ title: "ダッシュボード", noindex: true }),
13+
}),
1014
component: DashboardPage,
1115
});
1216

src/routes/_auth/shiftboard.$recruitmentId.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import { api } from "@/convex/_generated/api";
55
import type { Id } from "@/convex/_generated/dataModel";
66
import { ShiftBoardPage } from "@/src/components/features/ShiftBoard/ShiftBoardPage";
77
import { Animation } from "@/src/components/templates/Animation";
8+
import { buildMeta } from "@/src/helpers/seo";
89

910
export const Route = createFileRoute("/_auth/shiftboard/$recruitmentId")({
11+
head: () => ({
12+
meta: buildMeta({ title: "シフト表", noindex: true }),
13+
}),
1014
component: ShiftBoardRoute,
1115
});
1216

src/routes/_unregistered/shifts.reissue.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ import { ReissueDone } from "@/src/components/features/StaffView/ReissueDone";
99
import { ReissueForm } from "@/src/components/features/StaffView/ReissueForm";
1010
import { StaffLayout } from "@/src/components/templates/StaffLayout";
1111
import { FullPageSpinner } from "@/src/components/ui/FullPageSpinner";
12+
import { buildMeta } from "@/src/helpers/seo";
1213

1314
export const Route = createFileRoute("/_unregistered/shifts/reissue")({
1415
validateSearch: (search: Record<string, unknown>) => ({
1516
recruitmentId: search.recruitmentId as string,
1617
}),
18+
head: () => ({
19+
meta: buildMeta({ title: "シフト閲覧リンクの再発行", noindex: true }),
20+
}),
1721
component: ReissueRoute,
1822
});
1923

0 commit comments

Comments
 (0)