diff --git a/website/app/blog/[slug]/page.tsx b/website/app/blog/[slug]/page.tsx index 5be6933..c6d8ec3 100644 --- a/website/app/blog/[slug]/page.tsx +++ b/website/app/blog/[slug]/page.tsx @@ -23,7 +23,7 @@ export function generateStaticParams() { async function getPostContent(slug: string): Promise { try { - const filePath = path.join(process.cwd(), "content", "blog", `${slug}.md`); + const filePath = path.join(process.cwd(), "app", "blog", "posts", `${slug}.md`); const content = await fs.readFile(filePath, "utf-8"); return content; } catch { diff --git a/website/app/blog/page.tsx b/website/app/blog/page.tsx index 0173e1a..3545df9 100644 --- a/website/app/blog/page.tsx +++ b/website/app/blog/page.tsx @@ -1,124 +1,124 @@ -"use client"; -import React from "react"; -import Link from "next/link"; -import postsData from "./posts.json"; - -// Convert posts object to array and sort by date (newest first) -const BLOG_POSTS = Object.values(postsData).sort((a, b) => { - const dateA = new Date(a.date); - const dateB = new Date(b.date); - return dateB.getTime() - dateA.getTime(); -}); - -export default function BlogPage() { - return ( -
-
- - {/* Header */} -
-

- AutoClaw Blog -

-

- Insights on AI agent deployment, OpenClaw, and building the future of - personal AI assistants. -

-
- - {/* Featured Post (First one) */} -
- -
-
- {/* Fallback pattern since we don't have real images yet */} -
-
- -
-
-
-
- - {BLOG_POSTS[0].category} - - {BLOG_POSTS[0].date} - - {BLOG_POSTS[0].readTime} -
-

- {BLOG_POSTS[0].title} -

-

- {BLOG_POSTS[0].excerpt} -

-
- Read Article - - - -
-
-
- -
- - {/* Recent Posts Grid */} -
- {BLOG_POSTS.slice(1).map((post) => ( - -
-
- {/* Visual placeholder */} -
- - {post.title.charAt(0)} - -
-
- -
-
- {post.category} - {post.readTime} -
- -

- {post.title} -

- -

- {post.excerpt} -

- -
- {post.date} - Read more → -
-
- - ))} -
- - {/* Newsletter Signup */} -
-

Subscribe to our newsletter

-

- Get the latest updates on AutoClaw features, OpenClaw tips, and articles about AI agent deployment. -

-
e.preventDefault()}> - - -
-
- -
-
- ); -} +"use client"; +import React from "react"; +import Link from "next/link"; +import postsData from "./posts.json"; + +// Convert posts object to array and sort by date (newest first) +const BLOG_POSTS = Object.values(postsData).sort((a, b) => { + const dateA = new Date(a.date); + const dateB = new Date(b.date); + return dateB.getTime() - dateA.getTime(); +}); + +export default function BlogPage() { + return ( +
+
+ + {/* Header */} +
+

+ AutoClaw Blog +

+

+ Insights on AI agent deployment, OpenClaw, and building the future of + personal AI assistants. +

+
+ + {/* Featured Post (First one) */} +
+ +
+
+ {/* Fallback pattern since we don't have real images yet */} +
+
+ +
+
+
+
+ + {BLOG_POSTS[0].category} + + {BLOG_POSTS[0].date} + + {BLOG_POSTS[0].readTime} +
+

+ {BLOG_POSTS[0].title} +

+

+ {BLOG_POSTS[0].excerpt} +

+
+ Read Article + + + +
+
+
+ +
+ + {/* Recent Posts Grid */} +
+ {BLOG_POSTS.slice(1).map((post) => ( + +
+
+ {/* Visual placeholder */} +
+ + {post.title.charAt(0)} + +
+
+ +
+
+ {post.category} + {post.readTime} +
+ +

+ {post.title} +

+ +

+ {post.excerpt} +

+ +
+ {post.date} + Read more → +
+
+ + ))} +
+ + {/* Newsletter Signup */} +
+

Subscribe to our newsletter

+

+ Get the latest updates on AutoClaw features, OpenClaw tips, and articles about AI agent deployment. +

+
e.preventDefault()}> + + +
+
+ +
+
+ ); +} diff --git a/website/content/blog/curated-skills-philosophy.md b/website/app/blog/posts/curated-skills-philosophy.md similarity index 100% rename from website/content/blog/curated-skills-philosophy.md rename to website/app/blog/posts/curated-skills-philosophy.md diff --git a/website/content/blog/self-hosted-vs-cloud.md b/website/app/blog/posts/self-hosted-vs-cloud.md similarity index 100% rename from website/content/blog/self-hosted-vs-cloud.md rename to website/app/blog/posts/self-hosted-vs-cloud.md diff --git a/website/content/blog/understanding-api-costs.md b/website/app/blog/posts/understanding-api-costs.md similarity index 100% rename from website/content/blog/understanding-api-costs.md rename to website/app/blog/posts/understanding-api-costs.md diff --git a/website/content/blog/why-openclaw-deployment-hard.md b/website/app/blog/posts/why-openclaw-deployment-hard.md similarity index 100% rename from website/content/blog/why-openclaw-deployment-hard.md rename to website/app/blog/posts/why-openclaw-deployment-hard.md diff --git a/website/app/components/ConditionalNavbar.tsx b/website/app/components/ConditionalNavbar.tsx index 2cb8cc1..2081511 100644 --- a/website/app/components/ConditionalNavbar.tsx +++ b/website/app/components/ConditionalNavbar.tsx @@ -1,16 +1,16 @@ -"use client"; - -import { usePathname } from "next/navigation"; -import Sidebar from "./Sidebar"; - -export default function ConditionalNavbar() { - const pathname = usePathname(); - const isCallPage = pathname?.startsWith("/call"); - - if (isCallPage) { - return null; - } - - return ; -} - +"use client"; + +import { usePathname } from "next/navigation"; +import Sidebar from "./Sidebar"; + +export default function ConditionalNavbar() { + const pathname = usePathname(); + const isCallPage = pathname?.startsWith("/call"); + + if (isCallPage) { + return null; + } + + return ; +} + diff --git a/website/app/components/PageWrapper.tsx b/website/app/components/PageWrapper.tsx index d3c7996..712ee56 100644 --- a/website/app/components/PageWrapper.tsx +++ b/website/app/components/PageWrapper.tsx @@ -1,10 +1,10 @@ -"use client"; - -export default function PageWrapper({ children }: { children: React.ReactNode }) { - return ( -
- {children} -
- ); -} - +"use client"; + +export default function PageWrapper({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + diff --git a/website/app/components/WebVitals.tsx b/website/app/components/WebVitals.tsx new file mode 100644 index 0000000..5d73048 --- /dev/null +++ b/website/app/components/WebVitals.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useEffect } from "react"; +import { onCLS, onINP, onLCP, onFCP, onTTFB, type Metric } from "web-vitals"; + +function sendToAnalytics(metric: Metric) { + // Log in development + if (process.env.NODE_ENV === "development") { + console.log(`[Web Vitals] ${metric.name}:`, metric.value.toFixed(2), metric.rating); + } + + // TODO: Send to your analytics backend + // Examples: + // - Google Analytics: gtag('event', metric.name, { value: metric.value, ... }) + // - Custom endpoint: fetch('/api/vitals', { method: 'POST', body: JSON.stringify(metric) }) + // - Plausible: plausible(metric.name, { props: { value: metric.value } }) +} + +export function WebVitals() { + useEffect(() => { + onCLS(sendToAnalytics); + onINP(sendToAnalytics); + onLCP(sendToAnalytics); + onFCP(sendToAnalytics); + onTTFB(sendToAnalytics); + }, []); + + return null; +} diff --git a/website/app/layout.tsx b/website/app/layout.tsx index b9551b4..7a740d2 100644 --- a/website/app/layout.tsx +++ b/website/app/layout.tsx @@ -4,6 +4,7 @@ import { ClerkProvider } from "@clerk/nextjs"; import ConditionalNavbar from "./components/ConditionalNavbar"; import PageWrapper from "./components/PageWrapper"; import { ThemeProvider } from "./contexts/ThemeContext"; +import { WebVitals } from "./components/WebVitals"; export const metadata: Metadata = { title: "AutoClaw — Deploy OpenClaw in Minutes", @@ -83,6 +84,7 @@ export default function RootLayout({ > + {children} diff --git a/website/package.json b/website/package.json index 6f9be44..b92f374 100644 --- a/website/package.json +++ b/website/package.json @@ -16,7 +16,8 @@ "next": "^16.1.6", "react": "19.1.0", "react-dom": "19.1.0", - "react-markdown": "^10.1.0" + "react-markdown": "^10.1.0", + "web-vitals": "^5.1.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index 1f97c30..c5a30cb 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.2.11)(react@19.1.0) + web-vitals: + specifier: ^5.1.0 + version: 5.1.0 devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -2167,6 +2170,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + web-vitals@5.1.0: + resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -4663,6 +4669,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + web-vitals@5.1.0: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0