Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JsonLd } from "@/components/seo/JsonLd";
import { BASE_URL } from "@/lib/source";
import styles from "./page.module.css";
import { CompatibilitySection } from "./sections/CompatibilitySection/CompatibilitySection";
import { FeaturesSection } from "./sections/FeaturesSection/FeaturesSection";
Expand All @@ -9,9 +11,32 @@ import { ShiroMascot } from "./sections/ShiroMascot/ShiroMascot";
import { StepsSection } from "./sections/StepsSection/StepsSection";
import { TweetWallSection } from "./sections/TweetWallSection/TweetWallSection";

const jsonLd = [
{
"@context": "https://schema.org",
"@type": "Organization",
name: "OpenUI",
url: BASE_URL,
logo: `${BASE_URL}/favicon.svg`,
sameAs: ["https://github.com/thesysdev/openui", "https://discord.com/invite/Pbv5PsqUSv"],
},
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "OpenUI",
applicationCategory: "DeveloperApplication",
operatingSystem: "Web",
url: BASE_URL,
description:
"OpenUI is a full-stack Generative UI framework with a compact streaming-first language, a React runtime with built-in components, and ready-to-use chat interfaces.",
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
},
];

export default function HomePage() {
return (
<div className={styles.page}>
<JsonLd data={jsonLd} />
<div className={styles.heroShell}>
<HeroSection
showPlaygroundButton={false}
Expand Down
52 changes: 51 additions & 1 deletion docs/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PillLink } from "@/app/(home)/components/Button/Button";
import { blog } from "@/lib/source";
import { JsonLd } from "@/components/seo/JsonLd";
import { BASE_URL, blog, getBlogImage } from "@/lib/source";
import { getMDXComponents } from "@/mdx-components";
import { TOCItems } from "fumadocs-ui/components/toc/default";
import { TOCProvider, TOCScrollArea } from "fumadocs-ui/components/toc/index";
Expand All @@ -21,8 +22,38 @@ export default async function BlogPostPage(props: { params: Promise<{ slug: stri
if (!page) notFound();
const Mdx = page.data.body;

const url = `${BASE_URL}${page.url}`;
const imageUrl = `${BASE_URL}${getBlogImage(page).url}`;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: page.data.title,
description: page.data.description,
image: imageUrl,
datePublished: new Date(page.data.date).toISOString(),
author: { "@type": "Person", name: page.data.author },
publisher: {
"@type": "Organization",
name: "OpenUI",
logo: { "@type": "ImageObject", url: `${BASE_URL}/favicon.svg` },
},
mainEntityOfPage: { "@type": "WebPage", "@id": url },
url,
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Blog", item: `${BASE_URL}/blog` },
{ "@type": "ListItem", position: 2, name: page.data.title, item: url },
],
},
];

return (
<TOCProvider toc={page.data.toc}>
<JsonLd data={jsonLd} />
<main className="mx-auto flex w-full max-w-[1200px] gap-4 px-4 pt-16 pb-40 lg:gap-28 lg:pr-8 min-[1249px]:pl-0 min-[1024px]:max-[1248px]:pl-8">
<aside className="hidden w-56 shrink-0 lg:block">
<div className="sticky top-24">
Expand Down Expand Up @@ -68,9 +99,28 @@ export async function generateMetadata(props: {

if (!page) notFound();

const url = page.url;
const image = getBlogImage(page).url;

return {
title: page.data.title,
description: page.data.description,
alternates: { canonical: url },
openGraph: {
type: "article",
url,
title: page.data.title,
description: page.data.description,
publishedTime: new Date(page.data.date).toISOString(),
authors: [page.data.author],
images: [{ url: image, width: 1200, height: 630, alt: page.data.title }],
},
twitter: {
card: "summary_large_image",
title: page.data.title,
description: page.data.description,
images: [image],
},
};
}

Expand Down
54 changes: 53 additions & 1 deletion docs/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LLMCopyButton, ViewOptions } from "@/components/ai/page-actions";
import { JsonLd } from "@/components/seo/JsonLd";
import { gitConfig } from "@/lib/layout.shared";
import { getPageImage, source } from "@/lib/source";
import { BASE_URL, getPageImage, source } from "@/lib/source";
import { getMDXComponents } from "@/mdx-components";
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page";
import { createRelativeLink } from "fumadocs-ui/mdx";
Expand All @@ -14,8 +15,48 @@ export default async function Page(props: PageProps<"/docs/[[...slug]]">) {

const MDX = page.data.body;

const url = `${BASE_URL}${page.url}`;
const breadcrumbItems = [
{ name: "Docs", url: `${BASE_URL}/docs` },
...page.slugs.map((_, i) => {
const p = source.getPage(page.slugs.slice(0, i + 1));
return p ? { name: p.data.title, url: `${BASE_URL}${p.url}` } : null;
}),
].filter((item): item is { name: string; url: string } => item !== null);
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "TechArticle",
headline: page.data.title,
description: page.data.description,
image: `${BASE_URL}${getPageImage(page).url}`,
author: { "@type": "Organization", name: "OpenUI" },
publisher: {
"@type": "Organization",
name: "OpenUI",
logo: { "@type": "ImageObject", url: `${BASE_URL}/favicon.svg` },
},
...(page.data.lastModified
? { dateModified: new Date(page.data.lastModified).toISOString() }
: {}),
mainEntityOfPage: { "@type": "WebPage", "@id": url },
url,
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems.map((item, i) => ({
"@type": "ListItem",
position: i + 1,
name: item.name,
item: item.url,
})),
},
];

return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<JsonLd data={jsonLd} />
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription className="mb-0">{page.data.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b pb-6">
Expand Down Expand Up @@ -49,8 +90,19 @@ export async function generateMetadata(props: PageProps<"/docs/[[...slug]]">): P
return {
title: page.data.title,
description: page.data.description,
alternates: { canonical: page.url },
openGraph: {
type: "article",
url: page.url,
title: page.data.title,
description: page.data.description,
images: getPageImage(page).url,
},
twitter: {
card: "summary_large_image",
title: page.data.title,
description: page.data.description,
images: [getPageImage(page).url],
},
};
}
19 changes: 16 additions & 3 deletions docs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RootProvider } from "fumadocs-ui/provider/next";
import type { Metadata } from "next";
import type { Metadata, Viewport } from "next";
import { Geist_Mono, Inter } from "next/font/google";
import Script from "next/script";
import { BASE_URL } from "../lib/source";
Expand Down Expand Up @@ -47,16 +47,20 @@ export const metadata: Metadata = {
canonical: "/",
},
icons: {
icon: "/favicon.svg",
icon: [
{ url: "/favicon.svg", type: "image/svg+xml" },
{ url: "/favicon-32.png", type: "image/png", sizes: "32x32" },
],
shortcut: "/favicon.svg",
apple: "/favicon.svg",
apple: "/apple-touch-icon.png",
},
openGraph: {
type: "website",
url: "/",
siteName: SITE_TITLE,
title: SITE_TITLE,
description: SITE_DESCRIPTION,
locale: "en_US",
images: [
{
url: SITE_IMAGE,
Expand All @@ -68,6 +72,8 @@ export const metadata: Metadata = {
},
twitter: {
card: "summary_large_image",
site: "@thesysdev",
creator: "@thesysdev",
title: SITE_TITLE,
description: SITE_DESCRIPTION,
images: [SITE_IMAGE],
Expand All @@ -85,6 +91,13 @@ export const metadata: Metadata = {
},
};

export const viewport: Viewport = {
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#000000" },
],
};

export default function Layout({ children }: LayoutProps<"/">) {
return (
<html lang="en" className={`${inter.className} ${geistMono.variable}`} suppressHydrationWarning>
Expand Down
19 changes: 19 additions & 0 deletions docs/app/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { MetadataRoute } from "next";

export default function manifest(): MetadataRoute.Manifest {
return {
name: "OpenUI - The Open Standard for Generative UI",
short_name: "OpenUI",
description:
"OpenUI is a full-stack Generative UI framework with a compact streaming-first language, a React runtime with built-in components, and ready-to-use chat interfaces.",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#ffffff",
icons: [
{ src: "/favicon.svg", type: "image/svg+xml", sizes: "any" },
{ src: "/icon-192.png", type: "image/png", sizes: "192x192" },
{ src: "/icon-512.png", type: "image/png", sizes: "512x512" },
],
};
}
27 changes: 27 additions & 0 deletions docs/app/og/blog/[...slug]/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { blog, getBlogImage } from "@/lib/source";
import { ImageResponse } from "@takumi-rs/image-response";
import { generate as DefaultImage } from "fumadocs-ui/og/takumi";
import { notFound } from "next/navigation";

export const revalidate = false;

export async function GET(_req: Request, { params }: RouteContext<"/og/blog/[...slug]">) {
const { slug } = await params;
const page = blog.getPage(slug.slice(0, -1));
if (!page) notFound();

return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="OpenUI" />,
{
width: 1200,
height: 630,
format: "webp",
},
);
}

export function generateStaticParams() {
return blog.getPages().map((page) => ({
slug: getBlogImage(page).segments,
}));
}
22 changes: 22 additions & 0 deletions docs/app/playground/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import { WebsiteThemeProvider } from "@/components/website-theme-provider";
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./layout.css";

const TITLE = "Playground - Generate UI from a Prompt";
const DESCRIPTION =
"Build and preview generative UI live in your browser. Prompt an LLM and watch OpenUI render interactive components in real time - no setup required.";

export const metadata: Metadata = {
title: TITLE,
description: DESCRIPTION,
alternates: { canonical: "/playground" },
openGraph: {
type: "website",
url: "/playground",
title: TITLE,
description: DESCRIPTION,
},
twitter: {
card: "summary_large_image",
title: TITLE,
description: DESCRIPTION,
},
};

export default function PlaygroundLayout({ children }: { children: ReactNode }) {
return <WebsiteThemeProvider>{children}</WebsiteThemeProvider>;
}
6 changes: 3 additions & 3 deletions docs/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BASE_URL, blog, source } from "@/lib/source";

const STATIC_PATHS = ["/", "/playground", "/blog"];
const STATIC_PATHS = ["/", "/playground", "/blog", "/openclaw-os"];

export default async function sitemap() {
const staticRoutes = STATIC_PATHS.map((path) => ({
Expand All @@ -17,8 +17,8 @@ export default async function sitemap() {

const blogRoutes = blog.getPages().map((page) => ({
url: `${BASE_URL}${page.url}`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
lastModified: page.data.date ? new Date(page.data.date) : new Date(),
changeFrequency: "monthly" as const,
}));

return [...staticRoutes, ...docsRoutes, ...blogRoutes];
Expand Down
13 changes: 13 additions & 0 deletions docs/components/seo/JsonLd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Renders a JSON-LD structured-data block. Server-rendered so crawlers see it
* in the initial HTML. Pass any schema.org object (or array) as `data`.
*/
export function JsonLd({ data }: { data: Record<string, unknown> | Record<string, unknown>[] }) {
return (
<script
type="application/ld+json"
// JSON-LD must be inlined as raw text in the document head/body
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
9 changes: 9 additions & 0 deletions docs/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export function getPageImage(page: InferPageType<typeof source>) {
};
}

export function getBlogImage(page: InferPageType<typeof blog>) {
const segments = [...page.slugs, "image.webp"];

return {
segments,
url: `/og/blog/${segments.join("/")}`,
};
}

export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await page.data.getText("processed");

Expand Down
Binary file added docs/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/favicon-32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading