+
+ Guides
+
{
- const slugs = params._splat?.split("/") ?? [];
- const data = await getBlogPost({ data: slugs });
- await clientMdxLoader.preload(data.path);
- return data;
- },
- head: ({ loaderData }: { loaderData?: unknown }) => {
- const data = loaderData as
- | { title?: string; description?: string; url?: string }
- | undefined;
- const title = data?.title ?? "OpenSEO Blog";
- const description = data?.description;
- return buildPageSeo({
- title,
- description,
- path: data?.url ?? "/blogs",
- titleSuffix: "OpenSEO Blog",
- ogType: "article",
- });
- },
- component: BlogPost,
-});
-
-const clientMdxLoader = createClientLoader(blog, {
- id: "blog",
- component({ default: MDX }) {
- return (
-
-
-
- );
- },
-});
-
-function BlogPost() {
- const data = Route.useLoaderData() as {
- path: string;
- title: string;
- description?: string;
- };
- const Content = clientMdxLoader.getComponent(data.path);
-
- return (
-
-
-
- {data.title}
- {data.description && (
-
- {data.description}
-
- )}
-
-
-
-
-
-
- );
-}
diff --git a/web/src/routes/guides/$.tsx b/web/src/routes/guides/$.tsx
new file mode 100644
index 0000000..26f150a
--- /dev/null
+++ b/web/src/routes/guides/$.tsx
@@ -0,0 +1,99 @@
+import { createFileRoute, Link } from "@tanstack/react-router";
+import { HomeLayout } from "fumadocs-ui/layouts/home";
+import { createClientLoader } from "fumadocs-mdx/runtime/vite";
+import { DocsBody } from "fumadocs-ui/page";
+import defaultMdxComponents from "fumadocs-ui/mdx";
+import { baseOptions } from "@/lib/layout.shared";
+import { Suspense } from "react";
+import { getGuidePost } from "@/lib/content.functions";
+import { guide } from "../../../source.generated";
+import { buildPageSeo } from "@/lib/seo";
+
+export const Route = createFileRoute("/guides/$")({
+ loader: async ({ params }: { params: { _splat?: string } }) => {
+ const slugs = params._splat?.split("/") ?? [];
+ const data = await getGuidePost({ data: slugs });
+ await clientMdxLoader.preload(data.path);
+ return data;
+ },
+ head: ({ loaderData }: { loaderData?: unknown }) => {
+ const data = loaderData as
+ | { title?: string; description?: string; url?: string }
+ | undefined;
+ const title = data?.title ?? "OpenSEO Guides";
+ const description = data?.description;
+ return buildPageSeo({
+ title,
+ description,
+ path: data?.url ?? "/guides",
+ titleSuffix: "OpenSEO Guides",
+ ogType: "article",
+ });
+ },
+ component: GuidePost,
+});
+
+const clientMdxLoader = createClientLoader(guide, {
+ id: "guide",
+ component({ default: MDX }) {
+ return (
+
+
+
+ );
+ },
+});
+
+function GuidePost() {
+ const data = Route.useLoaderData() as {
+ path: string;
+ title: string;
+ description?: string;
+ };
+ const Content = clientMdxLoader.getComponent(data.path);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+function GuideHeader({
+ title,
+ description,
+}: {
+ title: string;
+ description?: string;
+}) {
+ return (
+
+ );
+}
diff --git a/web/src/routes/blogs/index.tsx b/web/src/routes/guides/index.tsx
similarity index 51%
rename from web/src/routes/blogs/index.tsx
rename to web/src/routes/guides/index.tsx
index 43ac8b8..c79a8a9 100644
--- a/web/src/routes/blogs/index.tsx
+++ b/web/src/routes/guides/index.tsx
@@ -1,49 +1,49 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { baseOptions } from "@/lib/layout.shared";
-import { getBlogPosts } from "@/lib/content.functions";
+import { getGuidePosts } from "@/lib/content.functions";
import { buildPageSeo } from "@/lib/seo";
-const blogIndexDescription = "Updates and guides from OpenSEO.";
+const guideIndexDescription = "Founder-focused guides from OpenSEO.";
-export const Route = createFileRoute("/blogs/")({
+export const Route = createFileRoute("/guides/")({
head: () =>
buildPageSeo({
- title: "OpenSEO Blog",
- description: blogIndexDescription,
- path: "/blogs",
+ title: "OpenSEO Guides",
+ description: guideIndexDescription,
+ path: "/guides",
}),
- component: BlogIndex,
- loader: async () => await getBlogPosts(),
+ component: GuideIndex,
+ loader: async () => await getGuidePosts(),
});
-function BlogIndex() {
- const posts = Route.useLoaderData();
+function GuideIndex() {
+ const guides = Route.useLoaderData();
return (
-
Blog
+
Guides
- {posts.length === 0 ? (
+ {guides.length === 0 ? (
- No blog posts yet. Check back soon.
+ No guides yet. Check back soon.
) : (
- {posts.map((post) => (
-
+ {guides.map((guide) => (
+
- {post.title}
+ {guide.title}
- {post.description && (
+ {guide.description && (
- {post.description}
+ {guide.description}
)}
diff --git a/web/src/routes/js/script[.]js.ts b/web/src/routes/js/script[.]js.ts
index 6ba56df..f90889e 100644
--- a/web/src/routes/js/script[.]js.ts
+++ b/web/src/routes/js/script[.]js.ts
@@ -7,25 +7,37 @@ export const Route = createFileRoute("/js/script.js")({
server: {
handlers: {
GET: async () => {
- const upstreamResponse = await fetch(PLAUSIBLE_SCRIPT_URL);
+ try {
+ const upstreamResponse = await fetch(PLAUSIBLE_SCRIPT_URL);
- if (!upstreamResponse.ok) {
- return new Response("Failed to load analytics script", {
- status: 502,
- headers: {
- "content-type": "text/plain; charset=utf-8",
- },
+ if (!upstreamResponse.ok) {
+ return buildFallbackScriptResponse();
+ }
+
+ const headers = new Headers(upstreamResponse.headers);
+ headers.set("cache-control", "public, max-age=86400, immutable");
+
+ return new Response(upstreamResponse.body, {
+ status: upstreamResponse.status,
+ headers,
});
+ } catch {
+ return buildFallbackScriptResponse();
}
-
- const headers = new Headers(upstreamResponse.headers);
- headers.set("cache-control", "public, max-age=86400, immutable");
-
- return new Response(upstreamResponse.body, {
- status: upstreamResponse.status,
- headers,
- });
},
},
},
});
+
+function buildFallbackScriptResponse() {
+ return new Response(
+ "window.plausible=window.plausible||function(){(window.plausible.q=window.plausible.q||[]).push(arguments)};",
+ {
+ status: 200,
+ headers: {
+ "content-type": "application/javascript; charset=utf-8",
+ "cache-control": "public, max-age=300",
+ },
+ },
+ );
+}