feat(blog): add blog functionality with multiple entries and categories - #748
Conversation
- Introduced a new blog collection with a defined schema for blog posts, including fields for title, summary, categories, and images. - Created a blog listing page that displays all blog entries with filtering options by category. - Added individual blog entry pages with metadata generation and support for Markdown content. - Included new blog images and markdown files for initial blog posts, enhancing the content available to users. - Updated navigation to include a link to the blog section. This commit enhances the user experience by providing a dedicated space for blog content, allowing for better engagement and information dissemination.
WalkthroughAdds a Blog content collection and three markdown posts, implements blog index, category, and slug pages plus a BlogList component and navbar link; updates changelog pages to use HeroRSC and layout tweaks; adds Next.js rewrites; enhances model page UI with capability badges and a playground link. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant NextApp as Next.js App Router
participant CC as content-collections
participant Markdown as Markdown renderer
participant UI as HeroRSC / BlogList / Footer
User->>NextApp: Request /blog/[slug]
activate NextApp
NextApp->>CC: fetch allBlogs()
CC-->>NextApp: entries[]
NextApp->>NextApp: find entry by slug or notFound()
NextApp->>Markdown: render entry.content
Markdown-->>NextApp: HTML
NextApp->>UI: Render HeroRSC + Article + Footer
UI-->>User: HTML response
deactivate NextApp
sequenceDiagram
autonumber
actor User
participant NextApp as Next.js App Router
participant CC as content-collections
participant UI as HeroRSC / BlogList
User->>NextApp: Request /blog/category/{category}
NextApp->>CC: fetch allBlogs()
CC-->>NextApp: entries[]
NextApp->>NextApp: filter !draft, match category, sort desc
NextApp->>UI: Render HeroRSC + BlogList(selectedCategory)
UI-->>User: HTML response
sequenceDiagram
autonumber
actor User
participant Next as Next.js Runtime
participant Rewrites as rewrites()
participant External as External site
User->>Next: GET /docs | /discord | /github | /twitter
Next->>Rewrites: match path
Rewrites-->>Next: destination URL
Next->>External: proxy request to destination
External-->>User: Response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ui/src/components/changelog/changelog.tsx (1)
75-83: Guard against missing images.If some entries omit
image, this will crash. Add a conditional.- <div className="bg-card border border-border rounded-lg overflow-hidden"> - <Link href={`/changelog/${entry.slug}`} prefetch={true}> - <Image - src={entry.image.src} - alt={entry.image.alt} - width={entry.image.width} - height={entry.image.height} - className="w-full h-64 object-cover hover:opacity-90 transition-opacity rounded-lg object-top" - /> - </Link> - </div> + {entry.image && ( + <div className="bg-card border border-border rounded-lg overflow-hidden"> + <Link href={`/changelog/${entry.slug}`} prefetch={true}> + <Image + src={entry.image.src} + alt={entry.image.alt || entry.title} + width={entry.image.width || 1200} + height={entry.image.height || 630} + className="w-full h-64 object-cover hover:opacity-90 transition-opacity rounded-lg object-top" + /> + </Link> + </div> + )}
🧹 Nitpick comments (15)
apps/ui/src/app/models/[name]/page.tsx (2)
26-33: Alignparamstyping with Next.js App Router conventions
paramsis synchronous in App Router. Typing it asPromiseforces unnecessaryawaitand fights Next types.-interface PageProps { - params: Promise<{ name: string }>; -} +interface PageProps { + params: { name: string }; +} -export default async function ModelPage({ params }: PageProps) { - const { name } = await params; +export default async function ModelPage({ params }: PageProps) { + const { name } = params; -export async function generateMetadata({ params }: PageProps) { - const { name } = await params; +export async function generateMetadata({ params }: PageProps) { + const { name } = params;If you’re on a bleeding-edge Next version that intentionally passes a promise here, keep as-is and disregard.
Also applies to: 268-271
115-124: Good: uses next/link for navigation in apps/ui
Link+ encoded query is correct per our guidelines. Minor nit:prefetchis true by default; you can omit it if not tuning behavior.apps/ui/src/components/landing/navbar.tsx (1)
122-136: Don’t wrap mailto: links with next/link.Using next/link for
mailto:can trigger prefetch warnings and is unnecessary. Render an<a>formailto:(and other external schemes) and useLinkfor internal routes.Apply conditionals in both desktop and mobile resource lists:
- <NavigationMenuLink asChild> - <Link - href={item.href} - className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" - prefetch={true} - > - <div className="text-sm font-medium leading-none"> - {item.name} - </div> - </Link> - </NavigationMenuLink> + <NavigationMenuLink asChild> + {item.href.startsWith("mailto:") ? ( + <a + href={item.href} + className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" + aria-label={item.name} + > + <div className="text-sm font-medium leading-none"> + {item.name} + </div> + </a> + ) : ( + <Link + href={item.href} + className="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground" + prefetch={true} + > + <div className="text-sm font-medium leading-none"> + {item.name} + </div> + </Link> + )} + </NavigationMenuLink>- <Link - href={item.href} - className="text-muted-foreground hover:text-accent-foreground block duration-150" - prefetch={true} - > - {item.name} - </Link> + {item.href.startsWith("mailto:") ? ( + <a + href={item.href} + className="text-muted-foreground hover:text-accent-foreground block duration-150" + aria-label={item.name} + > + {item.name} + </a> + ) : ( + <Link + href={item.href} + className="text-muted-foreground hover:text-accent-foreground block duration-150" + prefetch={true} + > + {item.name} + </Link> + )}Also applies to: 175-185
apps/ui/src/app/changelog/[slug]/page.tsx (1)
12-20: Typeparamsas an object, not a Promise.Next.js passes
paramsas a plain object; remove the Promise type andawait.-interface ChangelogEntryPageProps { - params: Promise<{ slug: string }>; -} +interface ChangelogEntryPageProps { + params: { slug: string }; +} @@ -export default async function ChangelogEntryPage({ - params, -}: ChangelogEntryPageProps) { - const { slug } = await params; +export default async function ChangelogEntryPage({ params }: ChangelogEntryPageProps) { + const { slug } = params; @@ -export async function generateMetadata({ params }: ChangelogEntryPageProps) { - const { slug } = await params; +export async function generateMetadata({ params }: ChangelogEntryPageProps) { + const { slug } = params;Also applies to: 93-101
apps/ui/src/components/changelog/changelog.tsx (1)
16-16: Replace non-standardpt-30.Use a valid Tailwind spacing token or an arbitrary value.
- <div className="bg-background text-foreground min-h-screen font-sans pt-30"> + <div className="bg-background text-foreground min-h-screen font-sans pt-28">apps/ui/content-collections.ts (1)
24-45: Harden blog schema: enforce date/slug formats and non-empty strings.Prevents malformed content and aligns with UI assumptions.
Apply this diff:
const blog = defineCollection({ name: "blog", directory: "src/content/blog", include: "**/*.md", schema: z.object({ id: z.string(), - slug: z.string(), - date: z.string(), + slug: z.string().regex(/^[a-z0-9-]+$/, "Use lowercase, digits, and hyphens only"), + date: z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD"), title: z.string(), summary: z.string(), draft: z.boolean().optional(), - categories: z.array(z.string()).default([]), + categories: z.array(z.string().min(1)).default([]), image: z .object({ src: z.string(), - alt: z.string(), + alt: z.string().min(1), width: z.number(), height: z.number(), }) .optional(), }), });apps/ui/src/app/blog/category/page.tsx (1)
1-3: Avoid empty route: redirect to /blog.Prevents a thin 200 page and improves UX/SEO.
Apply this diff:
-export default function Page() { - return null; -} +import { redirect } from "next/navigation"; +export default function Page() { + redirect("/blog"); +}apps/ui/src/app/blog/page.tsx (1)
35-50: Optional: make metadata static and typed.Minor DX improvement; current code is fine.
Apply this diff if you prefer a static export:
-export async function generateMetadata() { - return { +export const metadata = { title: "Blog - LLM Gateway", description: "News, tutorials, and deep-dives from the LLM Gateway team.", openGraph: { title: "Blog - LLM Gateway", description: "News, tutorials, and deep-dives from the LLM Gateway team.", type: "website", }, twitter: { card: "summary_large_image", title: "Blog - LLM Gateway", description: "News, tutorials, and deep-dives from the LLM Gateway team.", }, - }; -} +} as const;apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md (1)
42-48: Fix bare URLs and replace the raw GitHub link. Wrap each localhost endpoint in a markdown link and point to the public docs domain:- Web: http://localhost:3002 - Docs: http://localhost:3005 - API: http://localhost:4002 - Gateway: http://localhost:4001 + Web: [http://localhost:3002](http://localhost:3002) + Docs: [http://localhost:3005](http://localhost:3005) + API: [http://localhost:4002](http://localhost:4002) + Gateway: [http://localhost:4001](http://localhost:4001) - See the full guide: [`Self Host`](https://raw.githubusercontent.com/theopenco/llmgateway/refs/heads/main/apps/docs/content/self-host.mdx). + See the full guide: [Self Host](https://docs.llmgateway.com/self-host).If you’d rather link to
/docs/self-host, adjust your Next.js rewrite inapps/ui/next.config.tsto match dynamic routes (e.g.source: '/docs/:path*').apps/ui/src/components/blog/list.tsx (4)
23-28: Deduplicate slugify utility.slugify is duplicated across files. Consider extracting to a shared util to avoid drift.
43-45: Make category pills data-driven.Derive categories from content (and append “Changelog”) to avoid manual upkeep.
- const categoryList = ["Announcements", "Guides", "Engineering", "Changelog"]; + const derived = Array.from( + new Set(blogEntries.flatMap((e) => e.categories ?? [])), + ).filter((c) => c && c !== "Changelog"); + const categoryList = [...derived.sort(), "Changelog"];
56-83: Add aria-current for the active filter.Improves accessibility for keyboard/screen-reader users.
- <Link + <Link href="/blog" prefetch={true} - className={`px-4 py-2 rounded-full text-sm border transition-colors ${!selectedCategory ? "bg-foreground text-background" : "border-border hover:bg-muted"}`} + aria-current={!selectedCategory ? "page" : undefined} + className={`px-4 py-2 rounded-full text-sm border transition-colors ${!selectedCategory ? "bg-foreground text-background" : "border-border hover:bg-muted"}`} > @@ - <Link + <Link key={cat} href={ cat === "Changelog" ? "/changelog" : `/blog/category/${encodeURIComponent(catSlug)}` } prefetch={true} + aria-current={active ? "page" : undefined} className={`px-4 py-2 rounded-full text-sm border transition-colors ${active ? "bg-foreground text-background" : "border-border hover:bg-muted"}`} >
137-143: Use a time element for dates.Better semantics and machine-readability.
- <div className="text-xs text-muted-foreground"> - {new Date(entry.date).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - })} - </div> + <time + className="text-xs text-muted-foreground" + dateTime={entry.date} + > + {new Date(entry.date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + })} + </time>apps/ui/src/app/blog/category/[category]/page.tsx (1)
52-59: Exclude drafts from static params.Prevents generating category routes based solely on draft posts.
- const { allBlogs } = (await import("content-collections")) as any; + const { allBlogs } = await import("content-collections"); @@ - for (const post of allBlogs as any[]) { - (post.categories || []).forEach((c: string) => slugs.add(slugify(c))); + for (const post of allBlogs.filter((p) => !p?.draft)) { + (post.categories ?? []).forEach((c) => slugs.add(slugify(c)));apps/ui/src/app/blog/[slug]/page.tsx (1)
99-116: Consistent OpenGraph images object format (optional).Use object form for both primary and fallback for consistency.
- images: entry.image - ? [ - { - url: entry.image.src, - width: entry.image.width || 800, - height: entry.image.height || 400, - alt: entry.image.alt || entry.title, - }, - ] - : ["/opengraph.png"], + images: [ + entry.image + ? { + url: entry.image.src, + width: entry.image.width || 800, + height: entry.image.height || 400, + alt: entry.image.alt || entry.title, + } + : { url: "/opengraph.png", width: 1200, height: 630, alt: "LLM Gateway" }, + ],
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
apps/ui/public/blog/blog-introducing-llm-gateway.pngis excluded by!**/*.pngapps/ui/public/blog/custom-openai-compatible-providers.pngis excluded by!**/*.pngapps/ui/public/blog/how-to-self-host-llm-gateway.pngis excluded by!**/*.png
📒 Files selected for processing (15)
apps/ui/content-collections.ts(1 hunks)apps/ui/next.config.ts(1 hunks)apps/ui/src/app/blog/[slug]/page.tsx(1 hunks)apps/ui/src/app/blog/category/[category]/page.tsx(1 hunks)apps/ui/src/app/blog/category/page.tsx(1 hunks)apps/ui/src/app/blog/page.tsx(1 hunks)apps/ui/src/app/changelog/[slug]/page.tsx(2 hunks)apps/ui/src/app/changelog/page.tsx(2 hunks)apps/ui/src/app/models/[name]/page.tsx(4 hunks)apps/ui/src/components/blog/list.tsx(1 hunks)apps/ui/src/components/changelog/changelog.tsx(2 hunks)apps/ui/src/components/landing/navbar.tsx(1 hunks)apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md(1 hunks)apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md(1 hunks)apps/ui/src/content/blog/2025-05-10-custom-providers.md(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use localStorage instead of cookies for client-side data persistence
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In apps/ui (a tanstack router project), always use navigate() for navigation
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Never use
as anyor: anyin TypeScript files.
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
apps/{ui,docs}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use localStorage instead of cookies for client-side data persistence
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/ui/**/*.{ts,tsx,js,jsx}: In apps/ui (Next.js App Router), use next/link for links
In apps/ui, use next/navigation router.push/replace or redirect() for programmatic navigation
In apps/ui, never use window.location for navigation
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
apps/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/ui/**/*.{ts,tsx}: Use Next.js App Router (React Server Components) for the UI
Use next/link for links and next/navigation's router for programmatic navigation
Use TanStack Query for client/state data fetching and caching in the UI
Use Radix UI components with Tailwind CSS for UI building and styling
Prefer localStorage over cookies for client-side data persistence
Files:
apps/ui/src/components/landing/navbar.tsxapps/ui/src/app/blog/category/page.tsxapps/ui/src/app/blog/page.tsxapps/ui/src/app/changelog/page.tsxapps/ui/src/components/blog/list.tsxapps/ui/src/components/changelog/changelog.tsxapps/ui/content-collections.tsapps/ui/src/app/blog/category/[category]/page.tsxapps/ui/src/app/models/[name]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsxapps/ui/src/app/changelog/[slug]/page.tsx
**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always usedb().query.<table>.findMany()ordb().query.<table>.findFirst()
Files:
apps/ui/content-collections.tsapps/ui/next.config.ts
🧠 Learnings (3)
📚 Learning: 2025-09-02T00:39:46.758Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T00:39:46.758Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use Next.js App Router (React Server Components) for the UI
Applied to files:
apps/ui/src/app/blog/category/[category]/page.tsxapps/ui/next.config.tsapps/ui/src/app/blog/[slug]/page.tsx
📚 Learning: 2025-09-02T00:39:46.758Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T00:39:46.758Z
Learning: Applies to apps/ui/**/*.{ts,tsx} : Use next/link for links and next/navigation's router for programmatic navigation
Applied to files:
apps/ui/next.config.tsapps/ui/src/app/changelog/[slug]/page.tsx
📚 Learning: 2025-08-29T15:31:07.077Z
Learnt from: CR
PR: theopenco/llmgateway#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:31:07.077Z
Learning: Applies to apps/ui/**/*.{ts,tsx,js,jsx} : In apps/ui (Next.js App Router), use next/link for links
Applied to files:
apps/ui/src/app/changelog/[slug]/page.tsx
🧬 Code graph analysis (6)
apps/ui/src/app/blog/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
HeroRSC(4-10)apps/ui/src/components/blog/list.tsx (1)
BlogList(37-153)
apps/ui/src/app/changelog/page.tsx (1)
apps/ui/src/components/landing/hero-rsc.tsx (1)
HeroRSC(4-10)
apps/ui/src/components/blog/list.tsx (1)
apps/ui/src/components/landing/footer.tsx (1)
Footer(10-217)
apps/ui/src/app/blog/category/[category]/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
HeroRSC(4-10)apps/ui/src/components/blog/list.tsx (1)
BlogList(37-153)
apps/ui/src/app/blog/[slug]/page.tsx (3)
apps/ui/src/components/landing/hero-rsc.tsx (1)
HeroRSC(4-10)apps/ui/src/lib/utils/markdown.tsx (1)
getMarkdownOptions(126-192)apps/ui/src/components/landing/footer.tsx (1)
Footer(10-217)
apps/ui/src/app/changelog/[slug]/page.tsx (2)
apps/ui/src/components/landing/hero-rsc.tsx (1)
HeroRSC(4-10)apps/ui/src/lib/utils/markdown.tsx (1)
getMarkdownOptions(126-192)
🪛 LanguageTool
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md
[grammar] ~42-~42: There might be a mistake here.
Context: ...### Access - Web: http://localhost:3002 - Docs: http://localhost:3005 - API: http:...
(QB_NEW_EN)
[grammar] ~43-~43: There might be a mistake here.
Context: ...lhost:3002 - Docs: http://localhost:3005 - API: http://localhost:4002 - Gateway: ht...
(QB_NEW_EN)
[grammar] ~44-~44: There might be a mistake here.
Context: ...alhost:3005 - API: http://localhost:4002 - Gateway: http://localhost:4001 See the ...
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md
42-42: Bare URL used
(MD034, no-bare-urls)
43-43: Bare URL used
(MD034, no-bare-urls)
44-44: Bare URL used
(MD034, no-bare-urls)
45-45: Bare URL used
(MD034, no-bare-urls)
🔇 Additional comments (10)
apps/ui/src/app/models/[name]/page.tsx (4)
89-90: Good: user-facing title now reflectsmodelDef.nameImproves clarity when IDs differ from display names.
95-124: Good: header row wraps on small screens
flex-wrapon the badge/actions row prevents overflow and improves responsiveness.
235-236: Good: providers section references display nameConsistent with the header; better UX.
239-240: Copy tweak LGTMShorter explanation reads cleaner while preserving meaning.
apps/ui/src/components/landing/navbar.tsx (1)
31-31: Blog nav item — LGTM.Adds the route cleanly and uses next/link as expected.
apps/ui/src/app/changelog/page.tsx (1)
4-4: Header switch to HeroRSC — LGTM.Consistent with other pages; no data-path changes.
Also applies to: 17-19
apps/ui/src/app/changelog/[slug]/page.tsx (1)
28-30: Fix invalid Tailwind spacing and guard Next/Image dimensions
- No
tailwind.config.jsin the repo—pt-30isn’t a default spacing token; replace withpt-28,pt-32, or an arbitrary value (e.g.pt-[7.5rem]).- Verify that
entry.image.width/heighton the importedChangelogtype fromcontent-collectionsare always defined; if they’re optional, provide sensible defaults or conditionally render the<Image>to avoid runtime errors.apps/ui/src/components/changelog/changelog.tsx (1)
44-55: Nice sticky date block — LGTM.Good UX detail and readable formatting.
apps/ui/content-collections.ts (1)
48-49: LGTM: blog collection added to config.Configuration correctly includes the new collection.
apps/ui/src/content/blog/2025-05-10-custom-providers.md (1)
1-13: Front matter matches schema.Post metadata aligns with the defined blog schema.
| import { | ||
| AlertTriangle, | ||
| Play, | ||
| Zap, | ||
| Eye, | ||
| Wrench, | ||
| MessageSquare, | ||
| ImagePlus, | ||
| } from "lucide-react"; | ||
| import Link from "next/link"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Import LucideIcon type to eliminate downstream any usage
Bring in the icon type so the capability items can be strongly typed.
import {
AlertTriangle,
Play,
Zap,
Eye,
Wrench,
MessageSquare,
ImagePlus,
} from "lucide-react";
+import type { LucideIcon } from "lucide-react";
import Link from "next/link";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { | |
| AlertTriangle, | |
| Play, | |
| Zap, | |
| Eye, | |
| Wrench, | |
| MessageSquare, | |
| ImagePlus, | |
| } from "lucide-react"; | |
| import Link from "next/link"; | |
| import { | |
| AlertTriangle, | |
| Play, | |
| Zap, | |
| Eye, | |
| Wrench, | |
| MessageSquare, | |
| ImagePlus, | |
| } from "lucide-react"; | |
| import type { LucideIcon } from "lucide-react"; | |
| import Link from "next/link"; |
🤖 Prompt for AI Agents
In apps/ui/src/app/models/[name]/page.tsx around lines 7 to 16, the Lucide icon
imports are untyped leading to downstream any usage; import the LucideIcon type
from "lucide-react" (e.g. import type { LucideIcon } from "lucide-react") and
annotate the capability items/props with LucideIcon so the icon fields are
strongly typed (replace any occurrences of implicit any for icon props or
capability items with LucideIcon).
| src: "/blog/blog-introducing-llm-gateway.png" | ||
| alt: "LLM Gateway" | ||
| width: 2282 | ||
| height: 1198 |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify asset path and docs link resolve at runtime.
Ensure the image exists under the app’s public assets and that “/docs” resolves (page or rewrite).
Also applies to: 46-46
🏁 Script executed:
#!/bin/bash
# 1) Image exists somewhere in the repo (ideally apps/ui/public/blog/)
fd -HI --strip-cwd-prefix 'blog-introducing-llm-gateway.png'
# 2) Is there a local /docs route or a rewrite?
rg -nP -g 'apps/ui/**' '(href=.|Link.*href=.)["'\'']/docs["'\'']'
rg -nP 'rewrites\s*\(' next.config.* apps/ui/**/next.config.* 2>/dev/null || trueLength of output: 256
Configure /docs route or update link
I confirmed the image at /blog/blog-introducing-llm-gateway.png exists under apps/ui/public/blog, but I didn’t find any /docs page or rewrite in the UI app—this link will 404. Add a /docs route or a rewrite entry.
Location: apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md (lines 9–12, 46)
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/ui/src/types/content-collections.d.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use localStorage instead of cookies for client-side data persistence
Files:
apps/ui/src/types/content-collections.d.ts
**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{js,ts}: Use drizzle with the latest object syntax for database operations
For read queries, always usedb().query.<table>.findMany()ordb().query.<table>.findFirst()
Files:
apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In apps/ui (a tanstack router project), always use navigate() for navigation
Files:
apps/ui/src/types/content-collections.d.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/general.mdc)
Never use
as anyor: anyin TypeScript files.
Files:
apps/ui/src/types/content-collections.d.ts
apps/{ui,docs}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use localStorage instead of cookies for client-side data persistence
Files:
apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/ui/**/*.{ts,tsx,js,jsx}: In apps/ui (Next.js App Router), use next/link for links
In apps/ui, use next/navigation router.push/replace or redirect() for programmatic navigation
In apps/ui, never use window.location for navigation
Files:
apps/ui/src/types/content-collections.d.ts
apps/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/ui/**/*.{ts,tsx}: Use Next.js App Router (React Server Components) for the UI
Use next/link for links and next/navigation's router for programmatic navigation
Use TanStack Query for client/state data fetching and caching in the UI
Use Radix UI components with Tailwind CSS for UI building and styling
Prefer localStorage over cookies for client-side data persistence
Files:
apps/ui/src/types/content-collections.d.ts
🔇 Additional comments (1)
apps/ui/src/types/content-collections.d.ts (1)
20-33: Update Blog interface to match schema and usage
Reflect the Zod schema’sdraft(optional) andcategories(defaulted) fields, and makeimageoptional to align with optional chaining in consumers.--- a/apps/ui/src/types/content-collections.d.ts +++ b/apps/ui/src/types/content-collections.d.ts @@ export interface Blog { - image: { + categories: string[]; + draft?: boolean; + image?: { src: string; alt: string; width: number; height: number; };
This commit enhances the user experience by providing a dedicated space for blog content, allowing for better engagement and information dissemination.
Summary by CodeRabbit
New Features
UI
Content