plano/apps/www/src/app/blog/[slug]/layout.tsx
Musa 0c3efdbef2
feat: redesign archgw -> plano + website in Next.js (#613)
* feat: redesign archgw -> plano + website

* feat(www): refactor landing page sections, add new diagrams and UI improvements

* feat(www): sections enhanced for clarify & diagrams added

* feat(www): improvements to mobile design, layout of diagrams

* feat(www): clean + typecheck

* feat(www): feedback loop changes

* feat(www): fix type error

* fix lib/utils error

* feat(www): ran biome formatting

* feat(www): graphic changes

* feat(www): web analytics

* fea(www): changes

* feat(www): introduce monorepo

This change brings Turborepo monorepo to independently handle the marketing website, the docs website and any other future use cases for mutli-platform support. They are using internal @katanemo package handlers for the design system and logic.

* fix(www): transpiler failure

* fix(www): tsconfig issue

* fix(www): next.config issue

* feat(docs): hold off on docs

* Delete next.config.ts

* feat(www): content fix

* feat(www): introduce blog

* feat(www): content changes

* Update package-lock.json

* feat: update text

* Update IntroSection.tsx

* feat: Turbopack issue

* fix

* Update IntroSection.tsx

* feat: updated Research page

* refactor(www): text clarity, padding adj.

* format(www)

* fix: add missing lib/ files to git - fixes Vercel GitHub deployment

- Updated .gitignore to properly exclude Python lib/ but include Next.js lib/ directories
- Added packages/ui/src/lib/utils.ts (cn utility function)
- Added apps/www/src/lib/sanity.ts (Sanity client configuration)
- Fixes module resolution errors in Vercel GitHub deployments (case-sensitive filesystem)

* Update .gitignore

* style(www): favicon + metadata

* fix(www): links

* fix(www): add analytics

* fix(www): add

* fix(www): fix links + image

* fix(www): fix links + image

* fix(www): fix links

* fix(www): remove from tools testing.md
2025-12-18 15:55:15 -08:00

120 lines
3.1 KiB
TypeScript

import { Metadata } from "next";
import { client } from "@/lib/sanity";
type Params = Promise<{ slug: string }>;
interface BlogPost {
_id: string;
title: string;
slug: { current: string };
summary?: string;
publishedAt?: string;
author?: {
name?: string;
title?: string;
image?: any;
};
}
async function getBlogPost(slug: string): Promise<BlogPost | null> {
const query = `*[_type == "blog" && slug.current == $slug && published == true][0] {
_id,
title,
slug,
summary,
publishedAt,
author
}`;
const post = await client.fetch(query, { slug });
return post || null;
}
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
try {
const resolvedParams = await params;
const post = await getBlogPost(resolvedParams.slug);
if (!post) {
return {
title: "Post Not Found - Plano",
description: "The requested blog post could not be found.",
};
}
// Get baseUrl - use NEXT_PUBLIC_APP_URL if set, otherwise construct from VERCEL_URL
// Restrict to allowed hosts: localhost:3000, archgw-tau.vercel.app, or plano.katanemo.com
let baseUrl = "http://localhost:3000";
if (process.env.NEXT_PUBLIC_APP_URL) {
const url = process.env.NEXT_PUBLIC_APP_URL;
if (
url.includes("archgw-tau.vercel.app") ||
url.includes("plano.katanemo.com") ||
url.includes("localhost:3000")
) {
baseUrl = url;
}
} else if (process.env.VERCEL_URL) {
const hostname = process.env.VERCEL_URL;
// VERCEL_URL is just the hostname, not the full URL
if (hostname === "archgw-tau.vercel.app") {
baseUrl = `https://${hostname}`;
} else if (hostname === "plano.katanemo.com") {
baseUrl = `https://${hostname}`;
}
}
const ogImageUrl = `${baseUrl}/api/og/${resolvedParams.slug}`;
const metadata: Metadata = {
title: `${post.title} - Plano Blog`,
description: post.summary || "Read more on Plano Blog",
openGraph: {
title: post.title,
description: post.summary || "Read more on Plano Blog",
type: "article",
publishedTime: post.publishedAt,
authors: post.author?.name ? [post.author.name] : undefined,
url: `${baseUrl}/blog/${resolvedParams.slug}`,
siteName: "Plano",
images: [
{
url: ogImageUrl,
width: 1200,
height: 630,
alt: post.title,
},
],
locale: "en_US",
},
twitter: {
card: "summary_large_image",
title: post.title,
description: post.summary || "Read more on Plano Blog",
images: [ogImageUrl],
},
};
return metadata;
} catch (error) {
console.error("Error generating metadata:", error);
return {
title: "Blog Post - Plano",
description: "Read this post on Plano Blog",
};
}
}
interface LayoutProps {
children: React.ReactNode;
params: Params;
}
export default async function Layout({ children, params }: LayoutProps) {
return <>{children}</>;
}