): BlogSearch => {
+ const drafts = search.drafts === true || search.drafts === "" || search.drafts === "true";
+ return drafts ? { drafts: true } : {};
+ },
+ 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,
});
@@ -43,7 +51,7 @@ function PostRow({ slug, title, date, draft }: PostRowProps) {
function BlogIndex() {
const { drafts } = Route.useSearch();
- const posts = getPosts(drafts);
+ const posts = getPosts(drafts === true);
return (
diff --git a/packages/website/src/routes/changelog.tsx b/packages/website/src/routes/changelog.tsx
index 302552fed..1b76a95f1 100644
--- a/packages/website/src/routes/changelog.tsx
+++ b/packages/website/src/routes/changelog.tsx
@@ -5,12 +5,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta";
export const Route = createFileRoute("/changelog")({
- head: () => ({
- meta: pageMeta(
+ head: () =>
+ pageMeta(
"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,
});
diff --git a/packages/website/src/routes/cloud.tsx b/packages/website/src/routes/cloud.tsx
index eb6d514db..7de7036d6 100644
--- a/packages/website/src/routes/cloud.tsx
+++ b/packages/website/src/routes/cloud.tsx
@@ -6,12 +6,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta";
export const Route = createFileRoute("/cloud")({
- head: () => ({
- meta: pageMeta(
+ head: () =>
+ pageMeta(
"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,
});
diff --git a/packages/website/src/routes/docs/$.tsx b/packages/website/src/routes/docs/$.tsx
index fa8bc0082..8dd16902a 100644
--- a/packages/website/src/routes/docs/$.tsx
+++ b/packages/website/src/routes/docs/$.tsx
@@ -7,11 +7,10 @@ import { pageMeta } from "~/meta";
export const Route = createFileRoute("/docs/$")({
head: ({ params }) => {
const slug = params._splat ?? "";
+ const path = `/docs/${slug}`;
const doc = getDoc(slug);
- if (!doc) return { meta: pageMeta("Not Found - Paseo Docs", "Doc not found.") };
- return {
- meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description),
- };
+ if (!doc) return pageMeta("Not Found - Paseo Docs", "Doc not found.", path);
+ return pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description, path);
},
component: DocsPage,
});
diff --git a/packages/website/src/routes/docs/index.tsx b/packages/website/src/routes/docs/index.tsx
index 98652e360..b850d07a1 100644
--- a/packages/website/src/routes/docs/index.tsx
+++ b/packages/website/src/routes/docs/index.tsx
@@ -7,10 +7,13 @@ import { pageMeta } from "~/meta";
export const Route = createFileRoute("/docs/")({
head: () => {
const doc = getDoc("");
- if (!doc) return { meta: pageMeta("Docs - Paseo", "Paseo documentation.") };
- return {
- meta: pageMeta(`${doc.frontmatter.title} - Paseo Docs`, doc.frontmatter.description),
- };
+ if (!doc)
+ return pageMeta(
+ "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,
});
diff --git a/packages/website/src/routes/download.tsx b/packages/website/src/routes/download.tsx
index 4b32f0e75..8bf2cfaff 100644
--- a/packages/website/src/routes/download.tsx
+++ b/packages/website/src/routes/download.tsx
@@ -18,12 +18,12 @@ import { useRelease } from "~/routes/__root";
import "~/styles.css";
export const Route = createFileRoute("/download")({
- head: () => ({
- meta: pageMeta(
- "Download - Paseo",
- "Download Paseo for macOS, Windows, Linux, iOS, and Android. Your dev environment, in your pocket.",
+ head: () =>
+ pageMeta(
+ "Download Paseo for macOS, Windows, Linux, iOS, and Android",
+ "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,
});
diff --git a/packages/website/src/routes/index.tsx b/packages/website/src/routes/index.tsx
index 263e11d0d..e88ba9807 100644
--- a/packages/website/src/routes/index.tsx
+++ b/packages/website/src/routes/index.tsx
@@ -3,12 +3,12 @@ import { LandingPage } from "~/components/landing-page";
import { pageMeta } from "~/meta";
export const Route = createFileRoute("/")({
- head: () => ({
- meta: pageMeta(
- "Paseo – Run Claude Code, Codex, Copilot, OpenCode, and Pi from everywhere",
- "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.",
+ head: () =>
+ pageMeta(
+ "Paseo – Run Claude Code, Codex, Copilot, OpenCode from anywhere",
+ "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,
});
diff --git a/packages/website/src/routes/privacy.tsx b/packages/website/src/routes/privacy.tsx
index 2032d52e5..0f793eeaf 100644
--- a/packages/website/src/routes/privacy.tsx
+++ b/packages/website/src/routes/privacy.tsx
@@ -3,12 +3,12 @@ import { SiteShell } from "~/components/site-shell";
import { pageMeta } from "~/meta";
export const Route = createFileRoute("/privacy")({
- head: () => ({
- meta: pageMeta(
+ head: () =>
+ pageMeta(
"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,
});
diff --git a/packages/website/src/server-entry.ts b/packages/website/src/server-entry.ts
new file mode 100644
index 000000000..c6eed927c
--- /dev/null
+++ b/packages/website/src/server-entry.ts
@@ -0,0 +1,18 @@
+import startEntry from "@tanstack/react-start/server-entry";
+
+const CANONICAL_HOST = "paseo.sh";
+
+type FetchArgs = Parameters;
+
+export default {
+ async fetch(...args: FetchArgs): Promise {
+ 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);
+ },
+};
diff --git a/packages/website/vite.config.ts b/packages/website/vite.config.ts
index 056054eb1..d6c680621 100644
--- a/packages/website/vite.config.ts
+++ b/packages/website/vite.config.ts
@@ -53,6 +53,17 @@ function discoverAgentRoutes(): string[] {
.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 = [
"/",
"/agents",
@@ -62,6 +73,7 @@ const sitemapPages = [
"/privacy",
...discoverAgentRoutes(),
...discoverDocsRoutes(),
+ ...discoverBlogRoutes(),
].map((routePath) => ({
path: routePath,
}));
diff --git a/packages/website/wrangler.toml b/packages/website/wrangler.toml
index 64aa1dfd7..089a22089 100644
--- a/packages/website/wrangler.toml
+++ b/packages/website/wrangler.toml
@@ -2,7 +2,7 @@ name = "paseo-website"
account_id = "10ed39a1dbf316e30abd0c409bed40d6"
compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"]
-main = "@tanstack/react-start/server-entry"
+main = "./src/server-entry.ts"
routes = [
{ pattern = "paseo.sh", custom_domain = true },