nook: fix the landing title, isolate /blog, grow the sitemap

The landing page spent its strongest on-page signal on the word "Home";
the title now carries the positioning line and the OG title carries the
full brand phrase. /blog on a product subdomain served the personal blog
in dev (vinxi ignores the vercel.json rewrites that protect production),
splitting the brand and crawl equity — subdomain hits now render the
shared 404. /agents, /changelog and /faq join the sitemap registry so
they are indexed as they land.

Tests: bun test (539 pass); vinxi build clean, no new tsc errors.

Issues: FRE-8, FRE-15, FRE-9
This commit is contained in:
2026-10-04 23:22:04 -04:00
parent 44bf21a868
commit dc959d971b
4 changed files with 27 additions and 3 deletions

View File

@@ -83,6 +83,9 @@ export const SITEMAP_ROUTES: Record<SiteId, SitemapEntry[]> = {
nook: [
{ path: "/", changefreq: "weekly", priority: 1.0 },
{ path: "/checkout", changefreq: "monthly", priority: 0.5 },
{ path: "/privacy", changefreq: "yearly", priority: 0.4 }
{ path: "/privacy", changefreq: "yearly", priority: 0.4 },
{ path: "/agents", changefreq: "weekly", priority: 0.9 },
{ path: "/changelog", changefreq: "daily", priority: 0.7 },
{ path: "/faq", changefreq: "monthly", priority: 0.8 }
]
};

View File

@@ -21,6 +21,8 @@ import { api } from "~/lib/api";
import CustomScrollbar from "~/components/CustomScrollbar";
import "../post.css";
import { Post } from "~/db/types";
import { useSite } from "~/context/SiteContext";
import NotFound from "../../[...404]";
const getPostByTitle = query(
async (
@@ -262,6 +264,7 @@ const getPostByTitle = query(
);
export default function PostPage() {
const site = useSite();
const params = useParams();
const [searchParams] = useSearchParams();
@@ -297,6 +300,12 @@ export default function PostPage() {
return str.includes('data-type="mermaid"');
};
// Host-dispatch guard: blog posts live on the apex domain only (see the
// guard in blog/index.tsx for the dev-server rationale).
if (site().id !== "main") {
return <NotFound />;
}
return (
<Show
when={data()}

View File

@@ -9,6 +9,8 @@ import PostSorting from "~/components/blog/PostSorting";
import PublishStatusToggle from "~/components/blog/PublishStatusToggle";
import { TerminalSplash } from "~/components/TerminalSplash";
import { CACHE_CONFIG } from "~/config";
import { useSite } from "~/context/SiteContext";
import NotFound from "../[...404]";
const getPosts = query(async () => {
"use server";
@@ -77,6 +79,7 @@ const getPosts = query(async () => {
}, "posts");
export default function BlogIndex() {
const site = useSite();
const [searchParams] = useSearchParams();
const sort = () => searchParams.sort || "newest";
@@ -89,6 +92,15 @@ export default function BlogIndex() {
const data = createAsync(() => getPosts(), { deferStream: true });
// Host-dispatch guard: /blog is the personal blog on the apex domain only.
// Production rewrites already send `<sub>.freno.me/blog` → `/<sub>/blog`,
// which has no route — but in dev the vinxi server ignores vercel.json and
// would render the personal blog on a product subdomain, splitting the
// brand and crawl equity. Render the shared 404 instead.
if (site().id !== "main") {
return <NotFound />;
}
return (
<>
<EdgeCacheHeaders maxAge={60} staleSeconds={3600} />

View File

@@ -55,10 +55,10 @@ export default function NookLanding() {
<>
<EdgeCacheHeaders maxAge={300} />
<PageHead
title="Home"
title="Agent control for your Mac"
ogTitle="The Nook — agent control for your Mac"
description="The Nook — a native macOS utility for coding-agent orchestration, fan and thermal control."
ogImage={`https://${site().domain}/nook/og.png`}
ogTitle="The Nook — native macOS agent orchestration"
ogDescription="A one-time-purchase macOS app for coding-agent orchestration, fan control, and thermal insight."
/>