fix(website): SEO canonicals, host redirects, sitemap, app noindex

Addresses the high-volume issues from the Ahrefs audit:
- Worker entrypoint 301s every request not on https://paseo.sh
  (www subdomain and http variants), killing the duplicate-page
  errors and the HTTP→HTTPS internal-link notices.
- pageMeta() emits <link rel="canonical"> and og:url per route;
  every head() call now passes its canonical path.
- /blog stops rewriting itself to /blog?drafts=false; the search
  param only appears when explicitly true.
- Sitemap includes /blog and each post.
- Trim homepage title under SERP truncation, rewrite /agents meta
  description under 160 chars, pad short descriptions on
  /privacy, /download, /cloud, /changelog, /docs.
- app.paseo.sh gets <meta robots=noindex,nofollow> and a
  Disallow-all robots.txt so the SPA shell stops polluting the
  crawl with H1-missing / no-OG / low-word-count warnings.
This commit is contained in:
Mohamed Boudra
2026-05-21 10:07:41 +07:00
parent ea36f0879f
commit cae3deb6e7
17 changed files with 106 additions and 55 deletions
+1
View File
@@ -3,6 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex,nofollow" />
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"
+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /
@@ -5,7 +5,7 @@ import { pageMeta } from "~/meta";
export function agentRouteOptions(slug: string) { export function agentRouteOptions(slug: string) {
const page = getAgentPage(slug); const page = getAgentPage(slug);
return { return {
head: () => ({ meta: pageMeta(page.metaTitle, page.metaDescription) }), head: () => pageMeta(page.metaTitle, page.metaDescription, `/${slug}`),
component: function AgentLandingPage() { component: function AgentLandingPage() {
return <LandingPage title={page.title} subtitle={page.subtitle} />; return <LandingPage title={page.title} subtitle={page.subtitle} />;
}, },
+16 -7
View File
@@ -1,8 +1,17 @@
export function pageMeta(title: string, description: string) { const SITE_ORIGIN = "https://paseo.sh";
return [
{ title }, export function pageMeta(title: string, description: string, path: string) {
{ name: "description", content: description }, const url = `${SITE_ORIGIN}${path}`;
{ property: "og:title", content: title }, return {
{ property: "og:description", content: description }, meta: [
]; { title },
{ name: "description", content: description },
{ property: "og:title", content: title },
{ property: "og:description", content: description },
{ property: "og:url", content: url },
{ name: "twitter:title", content: title },
{ name: "twitter:description", content: description },
],
links: [{ rel: "canonical", href: url }],
};
} }
+5 -5
View File
@@ -7,12 +7,12 @@ import { pageMeta } from "~/meta";
import "~/styles.css"; import "~/styles.css";
export const Route = createFileRoute("/agents")({ export const Route = createFileRoute("/agents")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Supported agents – Every coding agent Paseo runs | Paseo", "Supported agents – Every coding agent Paseo runs",
"Run Claude Code, Codex, OpenCode, Cursor CLI, Gemini CLI, Hermes Agent, Qwen Code, Kimi Code, DeepSeek TUI, and 28 more coding agents from your phone. Self-hosted, your code stays on your machine.", "Run Claude Code, Codex, Copilot, OpenCode, Cursor CLI, Gemini CLI, and dozens more coding agents from your phone. Self-hosted, your code stays on your machine.",
"/agents",
), ),
}),
component: AgentsPage, component: AgentsPage,
}); });
+3 -4
View File
@@ -6,11 +6,10 @@ import { pageMeta } from "~/meta";
export const Route = createFileRoute("/blog/$")({ export const Route = createFileRoute("/blog/$")({
head: ({ params }) => { head: ({ params }) => {
const slug = params._splat ?? ""; const slug = params._splat ?? "";
const path = `/blog/${slug}`;
const post = getPost(slug); const post = getPost(slug);
if (!post) return { meta: pageMeta("Not Found - Paseo", "Post not found.") }; if (!post) return pageMeta("Not Found - Paseo", "Post not found.", path);
return { return pageMeta(`${post.frontmatter.title} - Paseo`, post.frontmatter.description, path);
meta: pageMeta(`${post.frontmatter.title} - Paseo`, post.frontmatter.description),
};
}, },
component: BlogPost, component: BlogPost,
}); });
+15 -7
View File
@@ -3,13 +3,21 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { getPosts, formatDate } from "~/posts"; import { getPosts, formatDate } from "~/posts";
import { pageMeta } from "~/meta"; import { pageMeta } from "~/meta";
interface BlogSearch {
drafts?: true;
}
export const Route = createFileRoute("/blog/")({ export const Route = createFileRoute("/blog/")({
validateSearch: (search: Record<string, unknown>) => ({ validateSearch: (search: Record<string, unknown>): BlogSearch => {
drafts: search.drafts === true || search.drafts === "" || search.drafts === "true", const drafts = search.drafts === true || search.drafts === "" || search.drafts === "true";
}), return drafts ? { drafts: true } : {};
head: () => ({ },
meta: pageMeta("Blog - Paseo", "Updates, thoughts, and announcements from the Paseo team."), head: () =>
}), pageMeta(
"Blog – Updates and announcements from the Paseo team",
"Product updates, technical posts, and announcements from the Paseo team. Notes on building a self-hosted, multi-agent dev environment for your phone.",
"/blog",
),
component: BlogIndex, component: BlogIndex,
}); });
@@ -43,7 +51,7 @@ function PostRow({ slug, title, date, draft }: PostRowProps) {
function BlogIndex() { function BlogIndex() {
const { drafts } = Route.useSearch(); const { drafts } = Route.useSearch();
const posts = getPosts(drafts); const posts = getPosts(drafts === true);
return ( return (
<div> <div>
+4 -4
View File
@@ -5,12 +5,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta"; import { pageMeta } from "~/meta";
export const Route = createFileRoute("/changelog")({ export const Route = createFileRoute("/changelog")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Changelog - Paseo", "Changelog - Paseo",
"Product updates, fixes, and improvements shipped in each Paseo release.", "Product updates, bug fixes, and improvements shipped in each Paseo release. Track new agent providers, mobile features, and daemon changes over time.",
"/changelog",
), ),
}),
component: Changelog, component: Changelog,
}); });
+4 -4
View File
@@ -6,12 +6,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta"; import { pageMeta } from "~/meta";
export const Route = createFileRoute("/cloud")({ export const Route = createFileRoute("/cloud")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Paseo Cloud - Design Partners", "Paseo Cloud - Design Partners",
"Paseo across machines, with a team, or inside a company. Looking for design partners.", "Run Paseo across machines, with a team, or inside a company. We're onboarding design partners for the hosted, multi-machine, multi-user version of Paseo.",
"/cloud",
), ),
}),
component: Cloud, component: Cloud,
}); });
+3 -4
View File
@@ -7,11 +7,10 @@ import { pageMeta } from "~/meta";
export const Route = createFileRoute("/docs/$")({ export const Route = createFileRoute("/docs/$")({
head: ({ params }) => { head: ({ params }) => {
const slug = params._splat ?? ""; const slug = params._splat ?? "";
const path = `/docs/${slug}`;
const doc = getDoc(slug); const doc = getDoc(slug);
if (!doc) return { meta: pageMeta("Not Found - Paseo Docs", "Doc not found.") }; if (!doc) return pageMeta("Not Found - Paseo Docs", "Doc not found.", path);
return { return pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description, path);
meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description),
};
}, },
component: DocsPage, component: DocsPage,
}); });
+7 -4
View File
@@ -7,10 +7,13 @@ import { pageMeta } from "~/meta";
export const Route = createFileRoute("/docs/")({ export const Route = createFileRoute("/docs/")({
head: () => { head: () => {
const doc = getDoc(""); const doc = getDoc("");
if (!doc) return { meta: pageMeta("Docs - Paseo", "Paseo documentation.") }; if (!doc)
return { return pageMeta(
meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description), "Docs - Paseo",
}; "Install Paseo and start running coding agents from your phone, desktop, and terminal.",
"/docs",
);
return pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description, "/docs");
}, },
component: DocsIndex, component: DocsIndex,
}); });
+5 -5
View File
@@ -18,12 +18,12 @@ import { useRelease } from "~/routes/__root";
import "~/styles.css"; import "~/styles.css";
export const Route = createFileRoute("/download")({ export const Route = createFileRoute("/download")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Download - Paseo", "Download Paseo for macOS, Windows, Linux, iOS, and Android",
"Download Paseo for macOS, Windows, Linux, iOS, and Android. Your dev environment, in your pocket.", "Install Paseo on every platform. Native desktop apps for macOS, Windows, and Linux. Mobile apps for iOS and Android. Self-hosted, open source, free to download.",
"/download",
), ),
}),
component: Download, component: Download,
}); });
+5 -5
View File
@@ -3,12 +3,12 @@ import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta"; import { pageMeta } from "~/meta";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Paseo – Run Claude Code, Codex, Copilot, OpenCode, and Pi from everywhere", "Paseo – Run Claude Code, Codex, Copilot, OpenCode from anywhere",
"A self-hosted daemon for Claude Code, Codex, Copilot, OpenCode, and Pi. Agents run on your machine with your full dev environment. Connect from phone, desktop, or web.", "Self-hosted daemon for Claude Code, Codex, Copilot, OpenCode, and Pi. Agents run on your machine with your full dev environment. Connect from phone, desktop, or web.",
"/",
), ),
}),
component: Home, component: Home,
}); });
+4 -4
View File
@@ -3,12 +3,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta"; import { pageMeta } from "~/meta";
export const Route = createFileRoute("/privacy")({ export const Route = createFileRoute("/privacy")({
head: () => ({ head: () =>
meta: pageMeta( pageMeta(
"Privacy Policy - Paseo", "Privacy Policy - Paseo",
"Privacy policy for Paseo - a self-hosted agent manager with no tracking or analytics.", "Privacy policy for Paseo, the self-hosted coding agent manager. No tracking, no analytics, no data collection. Your code stays on your machine.",
"/privacy",
), ),
}),
component: Privacy, component: Privacy,
}); });
+18
View File
@@ -0,0 +1,18 @@
import startEntry from "@tanstack/react-start/server-entry";
const CANONICAL_HOST = "paseo.sh";
type FetchArgs = Parameters<typeof startEntry.fetch>;
export default {
async fetch(...args: FetchArgs): Promise<Response> {
const [request] = args;
const url = new URL(request.url);
if (url.hostname !== CANONICAL_HOST || url.protocol !== "https:") {
url.protocol = "https:";
url.hostname = CANONICAL_HOST;
return Response.redirect(url.toString(), 301);
}
return startEntry.fetch(...args);
},
};
+12
View File
@@ -53,6 +53,17 @@ function discoverAgentRoutes(): string[] {
.map((slug) => `/${slug}`); .map((slug) => `/${slug}`);
} }
function discoverBlogRoutes(): string[] {
const postsDir = path.join(__dirname, "posts");
if (!fs.existsSync(postsDir)) return ["/blog"];
const slugs = fs
.readdirSync(postsDir, { withFileTypes: true })
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
.map((entry) => entry.name.replace(/\.md$/, ""))
.sort();
return ["/blog", ...slugs.map((slug) => `/blog/${slug}`)];
}
const sitemapPages = [ const sitemapPages = [
"/", "/",
"/agents", "/agents",
@@ -62,6 +73,7 @@ const sitemapPages = [
"/privacy", "/privacy",
...discoverAgentRoutes(), ...discoverAgentRoutes(),
...discoverDocsRoutes(), ...discoverDocsRoutes(),
...discoverBlogRoutes(),
].map((routePath) => ({ ].map((routePath) => ({
path: routePath, path: routePath,
})); }));
+1 -1
View File
@@ -2,7 +2,7 @@ name = "paseo-website"
account_id = "10ed39a1dbf316e30abd0c409bed40d6" account_id = "10ed39a1dbf316e30abd0c409bed40d6"
compatibility_date = "2024-12-01" compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"] compatibility_flags = ["nodejs_compat"]
main = "@tanstack/react-start/server-entry" main = "./src/server-entry.ts"
routes = [ routes = [
{ pattern = "paseo.sh", custom_domain = true }, { pattern = "paseo.sh", custom_domain = true },