@morphika/andami 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,7 @@ import type { Page } from "../../../lib/sanity/types";
|
|
|
7
7
|
import { PageRenderer } from "../../../components/blocks";
|
|
8
8
|
import { getSiteConfig } from "../../../lib/config";
|
|
9
9
|
import { assetUrl } from "../../../lib/assets";
|
|
10
|
+
import BreadcrumbJsonLd from "../../../components/seo/BreadcrumbJsonLd";
|
|
10
11
|
|
|
11
12
|
const cfg = getSiteConfig();
|
|
12
13
|
|
|
@@ -83,6 +84,12 @@ export default async function DynamicPage({ params }: PageProps) {
|
|
|
83
84
|
|
|
84
85
|
return (
|
|
85
86
|
<main className="min-h-screen">
|
|
87
|
+
<BreadcrumbJsonLd
|
|
88
|
+
items={[
|
|
89
|
+
{ name: "Home", path: "/" },
|
|
90
|
+
{ name: page.title },
|
|
91
|
+
]}
|
|
92
|
+
/>
|
|
86
93
|
<PageRenderer page={page} />
|
|
87
94
|
</main>
|
|
88
95
|
);
|
|
@@ -8,6 +8,7 @@ import { PageRenderer } from "../../../../components/blocks";
|
|
|
8
8
|
import { getSiteConfig } from "../../../../lib/config";
|
|
9
9
|
import { assetUrl } from "../../../../lib/assets";
|
|
10
10
|
import ProjectJsonLd from "../../../../components/seo/ProjectJsonLd";
|
|
11
|
+
import BreadcrumbJsonLd from "../../../../components/seo/BreadcrumbJsonLd";
|
|
11
12
|
|
|
12
13
|
const cfg = getSiteConfig();
|
|
13
14
|
|
|
@@ -85,6 +86,13 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
85
86
|
return (
|
|
86
87
|
<main className="min-h-screen">
|
|
87
88
|
<ProjectJsonLd page={page} />
|
|
89
|
+
<BreadcrumbJsonLd
|
|
90
|
+
items={[
|
|
91
|
+
{ name: "Home", path: "/" },
|
|
92
|
+
{ name: "Work", path: "/work" },
|
|
93
|
+
{ name: page.title },
|
|
94
|
+
]}
|
|
95
|
+
/>
|
|
88
96
|
<PageRenderer page={page} />
|
|
89
97
|
</main>
|
|
90
98
|
);
|
package/app/llms.txt/route.ts
CHANGED
|
@@ -96,8 +96,12 @@ export async function GET() {
|
|
|
96
96
|
lines.push("**Social profiles:**");
|
|
97
97
|
for (const link of settings.social_links) {
|
|
98
98
|
if (!link.url) continue;
|
|
99
|
-
const label = link.label
|
|
100
|
-
|
|
99
|
+
const label = link.label?.trim();
|
|
100
|
+
// Emit "Label: URL" only when a meaningful label is present (and is not
|
|
101
|
+
// just a duplicate of the URL). Otherwise emit the URL alone — avoids
|
|
102
|
+
// the "URL: URL" duplication when a user types the URL into both fields.
|
|
103
|
+
const showLabel = label && label !== link.url;
|
|
104
|
+
lines.push(showLabel ? `- ${label}: ${link.url}` : `- ${link.url}`);
|
|
101
105
|
}
|
|
102
106
|
lines.push("");
|
|
103
107
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BreadcrumbJsonLd — Server component that emits a `BreadcrumbList` JSON-LD
|
|
3
|
+
* record for nested pages. Helps Google understand site hierarchy and can
|
|
4
|
+
* replace the plain URL in SERP with visual breadcrumbs.
|
|
5
|
+
*
|
|
6
|
+
* Accepts items as relative paths (e.g. "/work") — the component resolves
|
|
7
|
+
* them against the configured domain so callers don't have to construct
|
|
8
|
+
* absolute URLs themselves. The last item typically omits `path` to indicate
|
|
9
|
+
* the current page.
|
|
10
|
+
*
|
|
11
|
+
* Example (project page):
|
|
12
|
+
* <BreadcrumbJsonLd items={[
|
|
13
|
+
* { name: "Home", path: "/" },
|
|
14
|
+
* { name: "Work", path: "/work" },
|
|
15
|
+
* { name: project.title },
|
|
16
|
+
* ]} />
|
|
17
|
+
*
|
|
18
|
+
* Returns null (via the underlying builder) when fewer than 2 items are
|
|
19
|
+
* supplied — a single-item breadcrumb is not a breadcrumb.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import JsonLd from "./JsonLd";
|
|
23
|
+
import { buildBreadcrumbJsonLd } from "../../lib/seo/jsonld";
|
|
24
|
+
import { getSiteConfig } from "../../lib/config";
|
|
25
|
+
|
|
26
|
+
interface BreadcrumbJsonLdItem {
|
|
27
|
+
/** Display name of the crumb. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Relative path (e.g. "/work"). Omit for the current page (last item). */
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface BreadcrumbJsonLdProps {
|
|
34
|
+
items: BreadcrumbJsonLdItem[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function BreadcrumbJsonLd({ items }: BreadcrumbJsonLdProps) {
|
|
38
|
+
const cfg = getSiteConfig();
|
|
39
|
+
const baseUrl = cfg.domain.replace(/\/$/, "");
|
|
40
|
+
|
|
41
|
+
const ld = buildBreadcrumbJsonLd({
|
|
42
|
+
items: items.map((item) => ({
|
|
43
|
+
name: item.name,
|
|
44
|
+
url: item.path ? `${baseUrl}${item.path.startsWith("/") ? "" : "/"}${item.path}` : undefined,
|
|
45
|
+
})),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return <JsonLd data={ld} />;
|
|
49
|
+
}
|
package/lib/seo/jsonld.ts
CHANGED
|
@@ -60,6 +60,18 @@ export interface CreativeWorkInput {
|
|
|
60
60
|
authorName?: string;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export interface BreadcrumbItem {
|
|
64
|
+
/** Display name of the crumb (e.g. "Work", "About"). */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Absolute URL of the crumb's page. Omit for the current page (last item). */
|
|
67
|
+
url?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface BreadcrumbInput {
|
|
71
|
+
/** Ordered list from root → current. First item is typically "Home". */
|
|
72
|
+
items: BreadcrumbItem[];
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
// ============================================
|
|
64
76
|
// Builders
|
|
65
77
|
// ============================================
|
|
@@ -172,3 +184,36 @@ export function buildCreativeWorkJsonLd(
|
|
|
172
184
|
|
|
173
185
|
return ld;
|
|
174
186
|
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* BreadcrumbList — emitted on nested pages (project detail, regular pages
|
|
190
|
+
* below the homepage). Helps Google understand site hierarchy and can replace
|
|
191
|
+
* the plain URL in SERP with visual breadcrumbs (`morphika.tv › Work › Udon`).
|
|
192
|
+
*
|
|
193
|
+
* Per schema.org guidance, the last item (the current page) typically omits
|
|
194
|
+
* the `item` URL — the trailing item is the page the breadcrumb is rendered
|
|
195
|
+
* on, so the URL is redundant.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when fewer than 2 items are supplied (a single-item breadcrumb
|
|
198
|
+
* is not a breadcrumb).
|
|
199
|
+
*/
|
|
200
|
+
export function buildBreadcrumbJsonLd(
|
|
201
|
+
input: BreadcrumbInput
|
|
202
|
+
): Record<string, unknown> | null {
|
|
203
|
+
const items = (input.items || []).filter((i) => i && i.name);
|
|
204
|
+
if (items.length < 2) return null;
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"@context": "https://schema.org",
|
|
208
|
+
"@type": "BreadcrumbList",
|
|
209
|
+
itemListElement: items.map((item, index) => {
|
|
210
|
+
const listItem: Record<string, unknown> = {
|
|
211
|
+
"@type": "ListItem",
|
|
212
|
+
position: index + 1,
|
|
213
|
+
name: item.name,
|
|
214
|
+
};
|
|
215
|
+
if (item.url) listItem.item = item.url;
|
|
216
|
+
return listItem;
|
|
217
|
+
}),
|
|
218
|
+
};
|
|
219
|
+
}
|
package/lib/version.ts
CHANGED
package/package.json
CHANGED
package/site/llms-txt.ts
CHANGED
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
* @morphika/andami/site/llms-txt — AI/LLM-friendly site summary route.
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the framework's /llms.txt route handler so instances can mount
|
|
5
|
-
* it at `/llms.txt`.
|
|
5
|
+
* it at `/llms.txt`.
|
|
6
|
+
*
|
|
7
|
+
* Required instance setup (one-time):
|
|
6
8
|
*
|
|
7
9
|
* // app/llms.txt/route.ts (in your instance repo)
|
|
8
|
-
*
|
|
10
|
+
* import "@/lib/config-init";
|
|
11
|
+
*
|
|
12
|
+
* // IMPORTANT: declare revalidate locally — Next.js requires
|
|
13
|
+
* // statically-parseable route segment config and rejects re-exports.
|
|
14
|
+
* export const revalidate = 86400;
|
|
15
|
+
*
|
|
16
|
+
* export { GET } from "@morphika/andami/site/llms-txt";
|
|
17
|
+
*
|
|
18
|
+
* Note: `revalidate` is re-exported from this barrel only as a convenience
|
|
19
|
+
* value for instances that want to reference the same default — it must
|
|
20
|
+
* NOT be re-exported via `export { revalidate } from ...` inside the
|
|
21
|
+
* instance's route file (Turbopack/Next will fail the build).
|
|
9
22
|
*/
|
|
10
23
|
export { GET, revalidate } from "../app/llms.txt/route";
|