From d268f1097e5fa74a7ac207a260f1464007c797f2 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 3 Oct 2025 11:55:35 -0500 Subject: [PATCH 1/5] feat: Reroute assets to /examples --- README.md | 2 +- apphosting.yaml | 2 +- next.config.ts | 6 ++++- src/app/action-context/app.tsx | 3 ++- src/app/api/og/route.tsx | 7 +++--- src/app/chatbot-hitl/app.tsx | 3 ++- src/app/chatbot-simple/page.tsx | 3 ++- src/app/image-analysis/app.tsx | 7 +++--- src/app/mcp/app.tsx | 3 ++- src/app/page.tsx | 5 ++-- src/app/robots.txt | 2 +- src/app/sitemap.ts | 6 +++-- src/app/structured-output/app.tsx | 3 ++- src/app/tool-calling/app.tsx | 3 ++- src/lib/constants.ts | 40 +++++++++++++++++++++++++++++++ src/lib/demo-metadata.ts | 5 ++-- src/lib/image-loader.ts | 29 ++++++++++++++++++++++ 17 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 src/lib/constants.ts create mode 100644 src/lib/image-loader.ts diff --git a/README.md b/README.md index ef07a9f..49ca153 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Genkit by Example -This repo contains the source code for the examples at [examples.genkit.dev](https://examples.genkit.dev). It is intended as an educational resource for developers wanting to add GenAI to their applications using Genkit. +This repo contains the source code for the examples at [genkit.dev/examples](https://genkit.dev/examples). It is intended as an educational resource for developers wanting to add GenAI to their applications using Genkit. diff --git a/apphosting.yaml b/apphosting.yaml index 644a1d3..5c17358 100644 --- a/apphosting.yaml +++ b/apphosting.yaml @@ -22,4 +22,4 @@ env: - variable: MAX_REQUESTS_HOURLY value: "50" - variable: SITE_ORIGIN - value: https://examples.genkit.dev + value: https://genkit.dev/examples diff --git a/next.config.ts b/next.config.ts index 4796dd1..9f03d26 100644 --- a/next.config.ts +++ b/next.config.ts @@ -17,7 +17,11 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + basePath: '/examples', + images: { + loader: 'custom', + loaderFile: './src/lib/image-loader.ts', + }, }; export default nextConfig; diff --git a/src/app/action-context/app.tsx b/src/app/action-context/app.tsx index a6cd548..2315881 100644 --- a/src/app/action-context/app.tsx +++ b/src/app/action-context/app.tsx @@ -19,13 +19,14 @@ import React, { useContext } from "react"; import Chat from "@/components/chat"; import DemoConfig from "@/lib/demo-config"; import CodeBlock from "@/components/code-block"; +import { withBasePath } from "@/lib/constants"; export default function ActionContextDemoApp() { const { config } = useContext(DemoConfig); return ( { if (info.message.role === "system") return <>; switch (part.toolResponse?.name) { diff --git a/src/app/api/og/route.tsx b/src/app/api/og/route.tsx index 6be4339..99cf0fe 100644 --- a/src/app/api/og/route.tsx +++ b/src/app/api/og/route.tsx @@ -19,6 +19,9 @@ import { NextRequest } from "next/server"; export const runtime = "edge"; +// Edge runtime doesn't support all Node.js APIs, so we inline the constant +const SITE_ORIGIN = process.env.SITE_ORIGIN || "http://localhost:3000/examples"; + export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); @@ -39,9 +42,7 @@ export async function GET(req: NextRequest) { padding: "40px", color: "white", fontFamily: "Nunito Sans", // Use Nunito Sans font (default) - backgroundImage: `url(${ - process.env.SITE_ORIGIN || "http://localhost:3000" - }/og_bg.png)`, + backgroundImage: `url(${SITE_ORIGIN}/og_bg.png)`, }} >

- + ); } diff --git a/src/app/image-analysis/app.tsx b/src/app/image-analysis/app.tsx index 5c10059..25044ff 100644 --- a/src/app/image-analysis/app.tsx +++ b/src/app/image-analysis/app.tsx @@ -26,6 +26,7 @@ import ImageField from "./image-field"; import HighlightArea from "./highlight-area"; import DemoConfig from "@/lib/demo-config"; import useAgent from "@/lib/use-agent"; +import { withBasePath } from "@/lib/constants"; function fileToDataUri(file: File): Promise { return new Promise((resolve, reject) => { @@ -57,8 +58,8 @@ export default function ImageAnalysisApp() { // Sample images const sampleImages = [ - { name: "Bird in Tree", path: "/image-analysis/bird_in_tree.png" }, - { name: "Desk Items", path: "/image-analysis/desk_items.jpg" }, + { name: "Bird in Tree", path: withBasePath("/image-analysis/bird_in_tree.png") }, + { name: "Desk Items", path: withBasePath("/image-analysis/desk_items.jpg") }, ]; function borderColor(o: Partial) { @@ -80,7 +81,7 @@ export default function ImageAnalysisApp() { const analysis = await simplePost< { imageUrl: string; system: any }, Analysis - >("/image-analysis/api", { + >(withBasePath("/image-analysis/api"), { system: config?.system, imageUrl: await fileToDataUri(file), }); diff --git a/src/app/mcp/app.tsx b/src/app/mcp/app.tsx index 437430e..cfb7e0d 100644 --- a/src/app/mcp/app.tsx +++ b/src/app/mcp/app.tsx @@ -19,6 +19,7 @@ import React from "react"; import Chat from "@/components/chat"; import { Sun, Cloud, CloudRain, CloudSnow } from "lucide-react"; import Dice from "./dice-roll"; +import { withBasePath } from "@/lib/constants"; function WeatherResponse({ temperature, @@ -78,7 +79,7 @@ function DiceResponse({ output }: { output: number }) { export default function ToolCallingChatbotApp() { return ( { if (part.toolResponse?.name === "getWeather") return ; diff --git a/src/app/page.tsx b/src/app/page.tsx index 1f2de23..0cdf27f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -19,6 +19,7 @@ import { demos } from "@/data"; import { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; +import { SITE_ORIGIN } from "@/lib/constants"; export const metadata: Metadata = { title: "Add GenAI to your web/mobile app", @@ -26,9 +27,7 @@ export const metadata: Metadata = { "Pre-built examples of using Genkit to build a variety of AI-powered features for Next.js applications.", openGraph: { images: [ - `${ - process.env.SITE_ORIGIN || "http://localhost:3000" - }/api/og?title=Practical%20GenAI%20 Demos`, + `${SITE_ORIGIN}/api/og?title=Practical%20GenAI%20 Demos`, ], }, }; diff --git a/src/app/robots.txt b/src/app/robots.txt index a03bdcc..e78e7ea 100644 --- a/src/app/robots.txt +++ b/src/app/robots.txt @@ -1,4 +1,4 @@ User-Agent: * Allow: / -Sitemap: https://examples.genkit.dev/sitemap.xml \ No newline at end of file +Sitemap: https://genkit.dev/examples/sitemap.xml \ No newline at end of file diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 109a543..1183e03 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,5 +1,6 @@ import { demos } from "@/data"; import type { MetadataRoute } from "next"; +import { SITE_ORIGIN } from "@/lib/constants"; const latestUpdate = demos .map((d) => d.added) @@ -8,15 +9,16 @@ const latestUpdate = demos .at(-1); export default function sitemap(): MetadataRoute.Sitemap { + const baseUrl = SITE_ORIGIN; return [ { - url: "https://examples.genkit.dev", + url: baseUrl, lastModified: latestUpdate, changeFrequency: "daily", priority: 1, }, ...demos.map((d) => ({ - url: `https://examples.genkit.dev/${d.id}`, + url: `${baseUrl}/${d.id}`, lastModified: d.added, changeFrequency: "weekly" as const, priority: 0.8, diff --git a/src/app/structured-output/app.tsx b/src/app/structured-output/app.tsx index ae3be7f..27bdf25 100644 --- a/src/app/structured-output/app.tsx +++ b/src/app/structured-output/app.tsx @@ -24,6 +24,7 @@ import type { CharacterSheet } from "./schema"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import React from "react"; +import { withBasePath } from "@/lib/constants"; export default function StructuredOutputApp() { const [prompt, setPrompt] = useState(""); @@ -47,7 +48,7 @@ export default function StructuredOutputApp() { const stream = post< { prompt: string }, { output: Partial } - >("/structured-output/api", { + >(withBasePath("/structured-output/api"), { prompt, }); for await (const chunk of stream) { diff --git a/src/app/tool-calling/app.tsx b/src/app/tool-calling/app.tsx index 0648bd5..ce1cde2 100644 --- a/src/app/tool-calling/app.tsx +++ b/src/app/tool-calling/app.tsx @@ -19,6 +19,7 @@ import React from "react"; import Chat from "@/components/chat"; import { Sun, Cloud, CloudRain, CloudSnow } from "lucide-react"; import Dice from "./dice-roll"; +import { withBasePath } from "@/lib/constants"; function WeatherResponse({ temperature, @@ -78,7 +79,7 @@ function DiceResponse({ output }: { output: number }) { export default function ToolCallingChatbotApp() { return ( { if (part.toolResponse?.name === "getWeather") return ; diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..d7a5d84 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Base path for the application - must match next.config.ts basePath + */ +export const BASE_PATH = "/examples"; + +/** + * Default base URL for the application (used in development) + * In production, SITE_ORIGIN env var should be set in apphosting.yaml + */ +export const DEFAULT_BASE_URL = `http://localhost:3000${BASE_PATH}`; + +/** + * Get the site origin (production or development) + */ +export const SITE_ORIGIN = process.env.SITE_ORIGIN || DEFAULT_BASE_URL; + +/** + * Helper to create paths with the basePath prefix + * Use this for any hardcoded paths that need the basePath added + */ +export function withBasePath(path: string): string { + return `${BASE_PATH}${path}`; +} + diff --git a/src/lib/demo-metadata.ts b/src/lib/demo-metadata.ts index c41ae63..9415c77 100644 --- a/src/lib/demo-metadata.ts +++ b/src/lib/demo-metadata.ts @@ -16,6 +16,7 @@ import { findDemo } from "@/data"; import { Metadata } from "next"; +import { SITE_ORIGIN } from "./constants"; export function demoMetadata(id: string): () => Promise { const demo = findDemo(id); @@ -26,9 +27,7 @@ export function demoMetadata(id: string): () => Promise { description: demo.description, openGraph: { images: [ - `${process.env.SITE_ORIGIN || "http://localhost:3000"}/api/og?title=${ - demo.name - }`, + `${SITE_ORIGIN}/api/og?title=${demo.name}`, ], description: demo.description, title: `Genkit by Example - ${demo.name}`, diff --git a/src/lib/image-loader.ts b/src/lib/image-loader.ts new file mode 100644 index 0000000..abf759f --- /dev/null +++ b/src/lib/image-loader.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Custom image loader to handle basePath correctly. + * + * Next.js Image Optimization has known issues with basePath where the optimizer + * doesn't fetch source images from the correct basePath location, resulting in + * 400 errors. This custom loader bypasses optimization and serves images directly. + * + * Related: https://github.com/vercel/next.js/issues/68498 + */ +export default function imageLoader({ src }: { src: string }) { + return `/examples${src}`; +} + From a7808aae55377588dc1ea0084e14597745f9412c Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:59:20 -0500 Subject: [PATCH 2/5] Simplify comments in image loader Removed unnecessary comments regarding basePath issues. --- src/lib/image-loader.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib/image-loader.ts b/src/lib/image-loader.ts index abf759f..96a85f7 100644 --- a/src/lib/image-loader.ts +++ b/src/lib/image-loader.ts @@ -15,13 +15,7 @@ */ /** - * Custom image loader to handle basePath correctly. - * - * Next.js Image Optimization has known issues with basePath where the optimizer - * doesn't fetch source images from the correct basePath location, resulting in - * 400 errors. This custom loader bypasses optimization and serves images directly. - * - * Related: https://github.com/vercel/next.js/issues/68498 + * Custom image loader to handle basePath correctly */ export default function imageLoader({ src }: { src: string }) { return `/examples${src}`; From 46ba0680452638c83263e771419c550224eff1d9 Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:00:46 -0500 Subject: [PATCH 3/5] Update image-loader.ts --- src/lib/image-loader.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/image-loader.ts b/src/lib/image-loader.ts index 96a85f7..f2ce2df 100644 --- a/src/lib/image-loader.ts +++ b/src/lib/image-loader.ts @@ -15,7 +15,8 @@ */ /** - * Custom image loader to handle basePath correctly + * TODO: Ran into issues with builtin NextJS image optimization serving the wrong image after configuring custom basePath. + * Custom loader lets us control that but turns off the automatic optimziation. */ export default function imageLoader({ src }: { src: string }) { return `/examples${src}`; From bad9b3ca2a5648d3744d276f980e38d9d56586c0 Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:08:23 -0500 Subject: [PATCH 4/5] Update route.tsx --- src/app/api/og/route.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/api/og/route.tsx b/src/app/api/og/route.tsx index 99cf0fe..79d82ba 100644 --- a/src/app/api/og/route.tsx +++ b/src/app/api/og/route.tsx @@ -16,12 +16,10 @@ import { ImageResponse } from "@vercel/og"; import { NextRequest } from "next/server"; +import { SITE_ORIGIN } from "@/lib/constants"; export const runtime = "edge"; -// Edge runtime doesn't support all Node.js APIs, so we inline the constant -const SITE_ORIGIN = process.env.SITE_ORIGIN || "http://localhost:3000/examples"; - export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); From da787490fa09e177e7315ae35e17b3ad479518fb Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:14:26 -0500 Subject: [PATCH 5/5] Update apphosting.yaml --- apphosting.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apphosting.yaml b/apphosting.yaml index 5c17358..644a1d3 100644 --- a/apphosting.yaml +++ b/apphosting.yaml @@ -22,4 +22,4 @@ env: - variable: MAX_REQUESTS_HOURLY value: "50" - variable: SITE_ORIGIN - value: https://genkit.dev/examples + value: https://examples.genkit.dev