From 30fd9ddad2c167ac97e027d807cbd360fd00b48a Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sat, 17 Jan 2026 23:35:16 +0700 Subject: [PATCH 01/13] feat: init blog --- .../src/app/(landings)/blog/[slug]/page.tsx | 80 +++++++++++++++++ .../blog/_components/BlogBreadcrumbs.tsx | 50 +++++++++++ .../(landings)/blog/_components/BlogCTA.tsx | 28 ++++++ .../(landings)/blog/_components/BlogCard.tsx | 47 ++++++++++ .../blog/_components/BlogCodeBlock.tsx | 26 ++++++ .../blog/_components/BlogHeading.tsx | 21 +++++ .../(landings)/blog/_components/BlogImage.tsx | 28 ++++++ .../blog/_components/BlogPostHeader.tsx | 47 ++++++++++ .../(landings)/blog/_components/BlogQuote.tsx | 15 ++++ .../blog/_components/BlogSidebar.tsx | 83 +++++++++++++++++ .../(landings)/blog/_components/BlogTOC.tsx | 78 ++++++++++++++++ .../app/(landings)/blog/_components/index.ts | 10 +++ .../blog/_posts/placeholder-images-guide.tsx | 90 +++++++++++++++++++ .../src/app/(landings)/blog/blog-posts.json | 22 +++++ apps/landing/src/app/(landings)/blog/page.tsx | 38 ++++++++ apps/landing/src/app/(landings)/blog/types.ts | 29 ++++++ apps/landing/src/app/(landings)/blog/utils.ts | 35 ++++++++ apps/landing/src/config/footer.ts | 1 + 18 files changed, 728 insertions(+) create mode 100644 apps/landing/src/app/(landings)/blog/[slug]/page.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogBreadcrumbs.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogCTA.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogCard.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogCodeBlock.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogHeading.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogImage.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogPostHeader.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogQuote.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogTOC.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/index.ts create mode 100644 apps/landing/src/app/(landings)/blog/_posts/placeholder-images-guide.tsx create mode 100644 apps/landing/src/app/(landings)/blog/blog-posts.json create mode 100644 apps/landing/src/app/(landings)/blog/page.tsx create mode 100644 apps/landing/src/app/(landings)/blog/types.ts create mode 100644 apps/landing/src/app/(landings)/blog/utils.ts diff --git a/apps/landing/src/app/(landings)/blog/[slug]/page.tsx b/apps/landing/src/app/(landings)/blog/[slug]/page.tsx new file mode 100644 index 0000000..b4ee8a4 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/[slug]/page.tsx @@ -0,0 +1,80 @@ +import { notFound } from 'next/navigation'; +import type { Metadata } from 'next'; +import { + getAllPosts, + getPostBySlug, + getPostsBySlugs, + generatePostMetadata, +} from '../utils'; +import { + BlogPostHeader, + BlogBreadcrumbs, + BlogTOC, + BlogSidebar, +} from '../_components'; + +interface PageProps { + params: Promise<{ slug: string }>; +} + +export async function generateStaticParams() { + const posts = getAllPosts(); + return posts.map((post) => ({ slug: post.slug })); +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + const post = getPostBySlug(slug); + if (!post) return {}; + return generatePostMetadata(post); +} + +export default async function BlogPostPage({ params }: PageProps) { + const { slug } = await params; + const post = getPostBySlug(slug); + + if (!post) { + notFound(); + } + + const { Content, tocItems } = await import(`../_posts/${slug}`); + const relatedArticles = getPostsBySlugs(post.relatedArticles); + + return ( +
+
+
+ +
+
+ + + +
+
+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogBreadcrumbs.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogBreadcrumbs.tsx new file mode 100644 index 0000000..67cb5c4 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogBreadcrumbs.tsx @@ -0,0 +1,50 @@ +import Link from 'next/link'; +import { ChevronRight } from 'lucide-react'; + +interface BreadcrumbItem { + label: string; + href?: string; +} + +interface BlogBreadcrumbsProps { + items: BreadcrumbItem[]; + variant?: 'light' | 'dark'; +} + +export const BlogBreadcrumbs = ({ + items, + variant = 'dark', +}: BlogBreadcrumbsProps) => { + const textColor = variant === 'dark' ? 'text-gray-400' : 'text-gray-600'; + const hoverColor = + variant === 'dark' ? 'hover:text-white' : 'hover:text-gray-900'; + const activeColor = variant === 'dark' ? 'text-white' : 'text-gray-900'; + + return ( + + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogCTA.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogCTA.tsx new file mode 100644 index 0000000..24bcee7 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogCTA.tsx @@ -0,0 +1,28 @@ +import Link from 'next/link'; + +interface BlogCTAProps { + title: string; + description: string; + buttonText: string; + buttonHref: string; +} + +export const BlogCTA = ({ + title, + description, + buttonText, + buttonHref, +}: BlogCTAProps) => { + return ( +
+

{title}

+

{description}

+ + {buttonText} + +
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogCard.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogCard.tsx new file mode 100644 index 0000000..85bd49c --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogCard.tsx @@ -0,0 +1,47 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import type { BlogPost } from '../types'; +import { formatDate } from '../utils'; + +interface BlogCardProps { + post: BlogPost; +} + +export const BlogCard = ({ post }: BlogCardProps) => { + return ( + +
+ {post.title} +
+
+
{post.category}
+

{post.title}

+

+ {post.description} +

+
+
+ {post.author.name} + {post.author.name} +
+ {formatDate(post.date)} + {post.readTime} +
+
+ + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogCodeBlock.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogCodeBlock.tsx new file mode 100644 index 0000000..3cfdafa --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogCodeBlock.tsx @@ -0,0 +1,26 @@ +interface BlogCodeBlockProps { + children: string; + language?: string; +} + +export const BlogCodeBlock = ({ + children, + language = 'text', +}: BlogCodeBlockProps) => { + return ( +
+ {language && ( +
+ {language} +
+ )} +
+        {children}
+      
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogHeading.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogHeading.tsx new file mode 100644 index 0000000..185ff4c --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogHeading.tsx @@ -0,0 +1,21 @@ +interface BlogHeadingProps { + id: string; + level: 2 | 3; + children: React.ReactNode; +} + +export const BlogHeading = ({ id, level, children }: BlogHeadingProps) => { + const Tag = `h${level}` as const; + + const baseStyles = 'scroll-mt-24 text-gray-900 font-semibold'; + const levelStyles = { + 2: 'text-2xl mt-12 mb-4 first:mt-0', + 3: 'text-xl mt-8 mb-3', + }; + + return ( + + {children} + + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogImage.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogImage.tsx new file mode 100644 index 0000000..a869261 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogImage.tsx @@ -0,0 +1,28 @@ +import Image from 'next/image'; + +interface BlogImageProps { + src: string; + alt: string; + caption?: string; + fullWidth?: boolean; +} + +export const BlogImage = ({ + src, + alt, + caption, + fullWidth = false, +}: BlogImageProps) => { + return ( +
+
+ {alt} +
+ {caption && ( +
+ {caption} +
+ )} +
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogPostHeader.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogPostHeader.tsx new file mode 100644 index 0000000..0afd13d --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogPostHeader.tsx @@ -0,0 +1,47 @@ +import Image from 'next/image'; +import type { BlogPost } from '../types'; +import { formatDate } from '../utils'; + +interface BlogPostHeaderProps { + post: BlogPost; +} + +export const BlogPostHeader = ({ post }: BlogPostHeaderProps) => { + return ( +
+
+
{post.category}
+

{post.title}

+

{post.description}

+ +
+ {post.author.name} +
+
{post.author.name}
+
+ {formatDate(post.date)} · {post.readTime} +
+
+
+
+ +
+
+ {post.title} +
+
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogQuote.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogQuote.tsx new file mode 100644 index 0000000..fcb1a47 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogQuote.tsx @@ -0,0 +1,15 @@ +interface BlogQuoteProps { + children: React.ReactNode; + author?: string; +} + +export const BlogQuote = ({ children, author }: BlogQuoteProps) => { + return ( +
+

{children}

+ {author && ( +
— {author}
+ )} +
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx new file mode 100644 index 0000000..848493f --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx @@ -0,0 +1,83 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import { BookOpen, Code, FileText, ExternalLink } from 'lucide-react'; +import type { BlogPost, RelatedDoc } from '../types'; + +interface BlogSidebarProps { + relatedArticles: BlogPost[]; + relatedDocs: RelatedDoc[]; +} + +const iconMap: Record> = { + book: BookOpen, + code: Code, + file: FileText, +}; + +export const BlogSidebar = ({ + relatedArticles, + relatedDocs, +}: BlogSidebarProps) => { + if (relatedArticles.length === 0 && relatedDocs.length === 0) { + return null; + } + + return ( + + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogTOC.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogTOC.tsx new file mode 100644 index 0000000..2910f34 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogTOC.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import type { TocItem } from '../types'; + +interface BlogTOCProps { + items: TocItem[]; +} + +export const BlogTOC = ({ items }: BlogTOCProps) => { + const [activeId, setActiveId] = useState(''); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + }); + }, + { rootMargin: '-20% 0px -35% 0px' } + ); + + items.forEach((item) => { + const element = document.getElementById(item.id); + if (element) observer.observe(element); + }); + + return () => observer.disconnect(); + }, [items]); + + const scrollToSection = (id: string) => { + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }; + + if (items.length === 0) { + return null; + } + + return ( + + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/index.ts b/apps/landing/src/app/(landings)/blog/_components/index.ts new file mode 100644 index 0000000..47272ad --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/index.ts @@ -0,0 +1,10 @@ +export { BlogCard } from './BlogCard'; +export { BlogTOC } from './BlogTOC'; +export { BlogPostHeader } from './BlogPostHeader'; +export { BlogBreadcrumbs } from './BlogBreadcrumbs'; +export { BlogSidebar } from './BlogSidebar'; +export { BlogHeading } from './BlogHeading'; +export { BlogImage } from './BlogImage'; +export { BlogQuote } from './BlogQuote'; +export { BlogCTA } from './BlogCTA'; +export { BlogCodeBlock } from './BlogCodeBlock'; diff --git a/apps/landing/src/app/(landings)/blog/_posts/placeholder-images-guide.tsx b/apps/landing/src/app/(landings)/blog/_posts/placeholder-images-guide.tsx new file mode 100644 index 0000000..f5a38cd --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/placeholder-images-guide.tsx @@ -0,0 +1,90 @@ +import { + BlogHeading, + BlogImage, + BlogQuote, + BlogCTA, + BlogCodeBlock, +} from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'introduction', text: 'Introduction', level: 2 }, + { id: 'why-ai-placeholders', text: 'Why AI Placeholders?', level: 2 }, + { id: 'getting-started', text: 'Getting Started', level: 2 }, + { id: 'api-usage', text: 'API Usage', level: 3 }, + { id: 'next-steps', text: 'Next Steps', level: 2 }, +]; + +export const Content = () => ( + <> + + Introduction + +

+ Placeholder images have been a staple of web development for decades. + From simple gray boxes to stock photos, developers have always needed + a way to visualize layouts before final content is ready. +

+

+ Banatie takes this concept to the next level with AI-generated + contextual placeholders that actually match your design intent. +

+ + + Why AI Placeholders? + +

+ Traditional placeholder services give you random images that rarely + match your actual content needs. With AI-powered placeholders, you get + images that are contextually relevant to your project. +

+ + + Finally, placeholder images that actually look like what the final + product will have. No more explaining to clients why there are cats + everywhere. + + + + Getting Started + +

+ Getting started with Banatie is simple. You can use our CDN URLs + directly in your HTML or integrate with our API for more control. +

+ + + API Usage + +

+ Here is a simple example of how to use Banatie in your HTML: +

+ + + {`Office workspace`} + + +

+ The prompt parameter tells our AI what kind of image to generate. + Be descriptive for best results! +

+ + + Next Steps + +

+ Ready to start using AI-powered placeholders in your projects? + Check out our documentation for more detailed examples and API reference. +

+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/blog-posts.json b/apps/landing/src/app/(landings)/blog/blog-posts.json new file mode 100644 index 0000000..a1e6bea --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/blog-posts.json @@ -0,0 +1,22 @@ +{ + "posts": [ + { + "slug": "placeholder-images-guide", + "title": "Getting Started with AI Placeholder Images", + "description": "Learn how to use Banatie to generate contextual placeholder images for your projects.", + "heroImage": "/blog/placeholder-guide-hero.jpg", + "category": "guides", + "date": "2025-01-15", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "5 min", + "relatedArticles": [], + "relatedDocs": [ + { "title": "API Reference", "href": "/docs/api/", "icon": "code" }, + { "title": "Quick Start", "href": "/docs/guides/", "icon": "book" } + ] + } + ] +} diff --git a/apps/landing/src/app/(landings)/blog/page.tsx b/apps/landing/src/app/(landings)/blog/page.tsx new file mode 100644 index 0000000..9c92d04 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/page.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from 'next'; +import { getAllPosts } from './utils'; +import { BlogCard, BlogBreadcrumbs } from './_components'; + +export const metadata: Metadata = { + title: 'Blog | Banatie', + description: + 'Articles, guides, and updates about AI-powered placeholder images.', +}; + +export default function BlogPage() { + const posts = getAllPosts(); + + return ( +
+
+ + +

Blog

+

+ Articles, guides, and updates about AI-powered images. +

+ +
+ {posts.map((post) => ( + + ))} +
+ + {posts.length === 0 && ( +

+ No articles yet. Check back soon! +

+ )} +
+
+ ); +} diff --git a/apps/landing/src/app/(landings)/blog/types.ts b/apps/landing/src/app/(landings)/blog/types.ts new file mode 100644 index 0000000..dc7daca --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/types.ts @@ -0,0 +1,29 @@ +export interface BlogAuthor { + name: string; + avatar: string; +} + +export interface RelatedDoc { + title: string; + href: string; + icon: string; +} + +export interface BlogPost { + slug: string; + title: string; + description: string; + heroImage: string; + category: string; + date: string; + author: BlogAuthor; + readTime: string; + relatedArticles: string[]; + relatedDocs: RelatedDoc[]; +} + +export interface TocItem { + id: string; + text: string; + level: number; +} diff --git a/apps/landing/src/app/(landings)/blog/utils.ts b/apps/landing/src/app/(landings)/blog/utils.ts new file mode 100644 index 0000000..f7e5d3e --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/utils.ts @@ -0,0 +1,35 @@ +import type { Metadata } from 'next'; +import type { BlogPost } from './types'; +import postsData from './blog-posts.json'; + +export const getAllPosts = (): BlogPost[] => postsData.posts as BlogPost[]; + +export const getPostBySlug = (slug: string): BlogPost | undefined => + (postsData.posts as BlogPost[]).find((p) => p.slug === slug); + +export const getPostsBySlugs = (slugs: string[]): BlogPost[] => + slugs + .map((slug) => getPostBySlug(slug)) + .filter((post): post is BlogPost => post !== undefined); + +export const generatePostMetadata = (post: BlogPost): Metadata => ({ + title: post.title, + description: post.description, + openGraph: { + title: post.title, + description: post.description, + images: [post.heroImage], + type: 'article', + publishedTime: post.date, + authors: [post.author.name], + }, +}); + +export const formatDate = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); +}; diff --git a/apps/landing/src/config/footer.ts b/apps/landing/src/config/footer.ts index 1811c88..6f2d98c 100644 --- a/apps/landing/src/config/footer.ts +++ b/apps/landing/src/config/footer.ts @@ -1,4 +1,5 @@ export const footerLinks = [ + { label: 'Blog', href: '/blog/' }, { label: 'Documentation', href: '/docs/' }, { label: 'API Reference', href: '/docs/api/' }, // { label: 'Pricing', href: '#' }, From c71b699d662e0586eb98d8267eb2b2506f409110 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sun, 18 Jan 2026 00:35:52 +0700 Subject: [PATCH 02/13] update mcp --- .mcp.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.mcp.json b/.mcp.json index 11633fe..4074eca 100644 --- a/.mcp.json +++ b/.mcp.json @@ -42,11 +42,9 @@ "PERPLEXITY_TIMEOUT_MS": "600000" } }, - "browsermcp": { - "type": "stdio", + "chrome-devtools": { "command": "npx", - "args": ["-y", "@browsermcp/mcp@latest"], - "env": {} + "args": ["-y", "chrome-devtools-mcp@latest"] } } } From fbe85fe6c9b82327c2bf3edb243f7087ce136cf0 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sun, 18 Jan 2026 00:36:13 +0700 Subject: [PATCH 03/13] feat: adjust components --- .../src/app/(landings)/blog/[slug]/code.html | 445 ++++++++++++++++++ .../blog/_components/BlogSidebar.tsx | 50 +- .../blog/_posts/api-integration-tips.tsx | 112 +++++ .../_posts/design-workflow-optimization.tsx | 92 ++++ .../image-generation-best-practices.tsx | 97 ++++ .../blog/_posts/prompt-engineering-basics.tsx | 112 +++++ .../blog/_posts/use-cases-ecommerce.tsx | 109 +++++ .../src/app/(landings)/blog/blog-posts.json | 92 +++- 8 files changed, 1083 insertions(+), 26 deletions(-) create mode 100644 apps/landing/src/app/(landings)/blog/[slug]/code.html create mode 100644 apps/landing/src/app/(landings)/blog/_posts/api-integration-tips.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_posts/design-workflow-optimization.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_posts/image-generation-best-practices.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_posts/prompt-engineering-basics.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_posts/use-cases-ecommerce.tsx diff --git a/apps/landing/src/app/(landings)/blog/[slug]/code.html b/apps/landing/src/app/(landings)/blog/[slug]/code.html new file mode 100644 index 0000000..bf53d27 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/[slug]/code.html @@ -0,0 +1,445 @@ + + + + +Refined Technical Blog Article + + + + + + + + + +
+
+
+
+
+
+
+
+
+ +
+ + Engineering + + +schedule 8 min read + +
+

+ Optimizing Image Generation Pipelines at Scale +

+

+ Learn how we reduced latency by 40% using edge caching and predictive pre-generation strategies for our high-throughput API endpoints. +

+
+Author Avatar +
+
Alex Chen
+
Senior Infrastructure Engineer • Oct 24, 2023
+
+
+
+
+
+Abstract technical graphic showing network nodes +
+
+
+
+
+
+
+ $ latency --check
+ > 45ms (optimized) +
+
+
+
+
+
+
+
+
+
+ +
+
+

+ When we first launched Banatie's image generation API, we optimized for quality. But as our user base grew, so did the demand for speed. Here is how we tackled the challenge of delivering AI-generated assets in milliseconds. +

+

The Latency Bottleneck

+

+ Our initial architecture was straightforward: a request hits our API gateway, gets queued, processed by a GPU worker, and the resulting image is uploaded to S3. Simple, but slow. +

+

+ Users integrating our API into real-time applications needed faster response times. We identified two main areas for improvement: +

+
    +
  • Cold Starts: Spinning up new GPU instances took 2-3 minutes.
  • +
  • Network Overhead: Round trips between the inference server and storage added 200ms+.
  • +
+
+
+
+info +
+
+
Pro Tip: Analyze your P99
+

+ Don't just look at average latency. Your P99 (99th percentile) latency tells you the experience of your users during worst-case scenarios. Optimizing for P99 often yields the most stable system. +

+
+
+
+

Implementing Edge Caching

+

+ To solve the network overhead, we moved our delivery layer to the edge. By utilizing a global CDN, we could serve cached results instantly for repeated prompts. +

+
+
+
+
+
+
+
+middleware/cache-control.ts +
+ +
+
+
export function setCacheHeaders(res: Response) {
+  // Cache for 1 hour at the edge, validate stale in background
+  res.setHeader(
+    
+  );
+  // Custom tag for purging
+  res.setHeader();
+}
+
+
+

The Results

+

+ After deploying these changes, we saw a dramatic drop in TTFB (Time To First Byte). +

+
+ "The latency improvements were immediate. Our dashboard loads felt instantaneous compared to the previous version, directly impacting our user retention metrics." +
+
+
+Graph comparing latency before and after optimization +
+
+insert_chart +Latency reduction over a 24-hour period post-deployment +
+
+

Predictive Pre-Generation

+

+ For our enterprise clients, we introduced predictive generation. By analyzing usage patterns, we can pre-warm the cache with variations of commonly requested assets before the user even asks for them. +

+

+ This is particularly useful for e-commerce clients who update their catalogs at predictable times. +

+
+

Conclusion

+

+ Optimization is never finished. We are currently exploring WebAssembly for client-side resizing to further offload our servers. Stay tuned for Part 2! +

+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx index 848493f..b67d09b 100644 --- a/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx +++ b/apps/landing/src/app/(landings)/blog/_components/BlogSidebar.tsx @@ -24,6 +24,31 @@ export const BlogSidebar = ({ return ( ); }; diff --git a/apps/landing/src/app/(landings)/blog/_posts/api-integration-tips.tsx b/apps/landing/src/app/(landings)/blog/_posts/api-integration-tips.tsx new file mode 100644 index 0000000..17182f6 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/api-integration-tips.tsx @@ -0,0 +1,112 @@ +import { BlogHeading, BlogQuote, BlogCTA, BlogCodeBlock } from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'overview', text: 'Overview', level: 2 }, + { id: 'authentication', text: 'Authentication Setup', level: 2 }, + { id: 'error-handling', text: 'Error Handling', level: 2 }, + { id: 'rate-limits', text: 'Rate Limits', level: 3 }, + { id: 'caching', text: 'Caching Strategies', level: 2 }, + { id: 'conclusion', text: 'Conclusion', level: 2 }, +]; + +export const Content = () => ( + <> + + Overview + +

+ Integrating the Banatie API into your application opens up a world of + possibilities for dynamic image generation. This guide covers the best + practices for a smooth integration. +

+

+ Whether you are building a web application, mobile app, or backend service, + these tips will help you get the most out of the API. +

+ + + Authentication Setup + +

+ All API requests require authentication via an API key. Here is how to + set it up properly: +

+ + + {`const headers = { + 'X-API-Key': process.env.BANATIE_API_KEY, + 'Content-Type': 'application/json' +}; + +const response = await fetch('https://api.banatie.app/text-to-image', { + method: 'POST', + headers, + body: JSON.stringify({ prompt: 'modern office' }) +});`} + + + + Never expose your API key in client-side code. Always make API calls + from your server. + + + + Error Handling + +

+ Proper error handling ensures your application gracefully handles + failures. The API returns standard HTTP status codes. +

+ + + {`try { + const response = await generateImage(prompt); + if (!response.ok) { + if (response.status === 429) { + // Rate limited - implement backoff + } + throw new Error(\`API error: \${response.status}\`); + } +} catch (error) { + console.error('Image generation failed:', error); + // Show fallback image +}`} + + + + Rate Limits + +

+ The API has rate limits to ensure fair usage. Plan your requests + accordingly and implement exponential backoff for retries. +

+ + + Caching Strategies + +

+ Caching generated images can significantly reduce API calls and improve + response times. Consider caching at multiple levels. +

+

+ Use CDN caching for frequently requested images and local caching for + user-specific content. +

+ + + Conclusion + +

+ Following these best practices will help you build a robust integration + with the Banatie API. Start small, test thoroughly, and scale as needed. +

+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/_posts/design-workflow-optimization.tsx b/apps/landing/src/app/(landings)/blog/_posts/design-workflow-optimization.tsx new file mode 100644 index 0000000..161b5ee --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/design-workflow-optimization.tsx @@ -0,0 +1,92 @@ +import { BlogHeading, BlogQuote, BlogCTA } from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'introduction', text: 'Introduction', level: 2 }, + { id: 'traditional-workflow', text: 'Traditional Workflow Problems', level: 2 }, + { id: 'ai-powered-approach', text: 'AI-Powered Approach', level: 2 }, + { id: 'figma-integration', text: 'Figma Integration', level: 3 }, + { id: 'time-savings', text: 'Time Savings', level: 2 }, + { id: 'tips', text: 'Pro Tips', level: 2 }, +]; + +export const Content = () => ( + <> + + Introduction + +

+ Design workflows have traditionally been bottlenecked by the need for + placeholder content. Finding the right stock photos or waiting for + actual assets can slow down the creative process significantly. +

+

+ AI-generated placeholders offer a new paradigm where designers can + instantly visualize their concepts with contextually relevant images. +

+ + + Traditional Workflow Problems + +

+ The conventional design process often involves searching through stock + photo libraries, downloading images, and then realizing they do not quite + fit the vision. This cycle repeats multiple times per project. +

+ + + We used to spend hours just finding the right placeholder images. + Now we describe what we need and get it instantly. + + + + AI-Powered Approach + +

+ With AI-generated images, you simply describe what you need. Want a + hero image showing a team collaboration scene? Just ask for it. Need + product mockups with specific aesthetics? Describe the style. +

+

+ This approach keeps you in the creative flow instead of breaking + concentration to hunt for assets. +

+ + + Figma Integration + +

+ Many designers are using Banatie URLs directly in Figma prototypes. + This means your mockups automatically show relevant images without + manual image placement. +

+ + + Time Savings + +

+ Teams report saving 2-4 hours per project on asset hunting alone. + That time can now be spent on actual design work and iteration. +

+

+ The quick iteration cycle also means stakeholder feedback sessions + are more productive since designs look more complete. +

+ + + Pro Tips + +

+ Start with broad prompts and refine as needed. Use consistent style + descriptors across your project for visual coherence. Create a prompt + library for commonly used image types in your brand. +

+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/_posts/image-generation-best-practices.tsx b/apps/landing/src/app/(landings)/blog/_posts/image-generation-best-practices.tsx new file mode 100644 index 0000000..85a0bf6 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/image-generation-best-practices.tsx @@ -0,0 +1,97 @@ +import { BlogHeading, BlogQuote, BlogCTA } from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'introduction', text: 'Introduction', level: 2 }, + { id: 'quality-settings', text: 'Quality Settings', level: 2 }, + { id: 'resolution', text: 'Resolution Guidelines', level: 3 }, + { id: 'consistency', text: 'Maintaining Consistency', level: 2 }, + { id: 'performance', text: 'Performance Optimization', level: 2 }, + { id: 'dos-and-donts', text: 'Dos and Donts', level: 2 }, +]; + +export const Content = () => ( + <> + + Introduction + +

+ Generating high-quality AI images consistently requires understanding + both the capabilities and limitations of the technology. This guide + shares proven practices from thousands of successful generations. +

+

+ Following these guidelines will help you achieve better results with + fewer iterations. +

+ + + Quality Settings + +

+ The API offers different quality tiers. Higher quality settings produce + better images but take longer to generate. Choose based on your use case. +

+

+ For prototyping and development, standard quality is often sufficient. + Reserve high quality for production assets. +

+ + + Resolution Guidelines + +

+ Match the resolution to your actual display size. Generating 4K images + for thumbnail displays wastes resources and slows performance. +

+ + + Start with the exact dimensions you need. You can always regenerate + at higher resolution for final production. + + + + Maintaining Consistency + +

+ For projects requiring visual consistency across multiple images, use + consistent style descriptors and seed values. Document your successful + prompts for reuse. +

+

+ Creating a style guide for your project prompts helps team members + generate images that feel cohesive. +

+ + + Performance Optimization + +

+ Cache generated images when possible. Use lazy loading for below-the-fold + images. Consider generating images ahead of time for predictable content. +

+

+ Batch similar requests together when generating multiple images to + optimize API usage. +

+ + + Dos and Donts + +

+ Do: Test prompts iteratively, use specific descriptors, + cache results, match resolution to need. +

+

+ Do not: Use vague prompts, generate unnecessarily high + resolutions, skip error handling, expose API keys client-side. +

+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/_posts/prompt-engineering-basics.tsx b/apps/landing/src/app/(landings)/blog/_posts/prompt-engineering-basics.tsx new file mode 100644 index 0000000..4b92b31 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/prompt-engineering-basics.tsx @@ -0,0 +1,112 @@ +import { BlogHeading, BlogQuote, BlogCTA, BlogCodeBlock } from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'what-is-prompt-engineering', text: 'What is Prompt Engineering?', level: 2 }, + { id: 'basic-structure', text: 'Basic Prompt Structure', level: 2 }, + { id: 'subject', text: 'Subject', level: 3 }, + { id: 'style', text: 'Style', level: 3 }, + { id: 'context', text: 'Context', level: 3 }, + { id: 'common-mistakes', text: 'Common Mistakes', level: 2 }, + { id: 'advanced-techniques', text: 'Advanced Techniques', level: 2 }, +]; + +export const Content = () => ( + <> + + What is Prompt Engineering? + +

+ Prompt engineering is the art of crafting text descriptions that guide + AI models to generate the exact images you envision. It is a skill that + improves with practice and understanding of how AI interprets language. +

+

+ Good prompts are specific, descriptive, and structured in a way that + the AI can parse effectively. +

+ + + Basic Prompt Structure + +

+ A well-structured prompt typically includes three key elements: subject, + style, and context. Let us break down each component. +

+ + + Subject + +

+ The subject is what you want to see in the image. Be specific about + the main focus. +

+ + + {`Bad: "a person" +Good: "a young professional woman working at a laptop" +Better: "a young professional woman in business casual attire working at a MacBook in a modern office"`} + + + + Style + +

+ Style descriptors help define the visual aesthetic of the generated + image. +

+ + + {`Style keywords: +- "photorealistic" - for lifelike images +- "minimalist" - clean, simple compositions +- "vibrant colors" - saturated, energetic palette +- "soft lighting" - gentle, diffused light`} + + + + Context + +

+ Context provides background information and setting details. +

+ + + The more context you provide, the more aligned the result will be + with your vision. But avoid being so specific that you constrain + the AI too much. + + + + Common Mistakes + +

+ Avoid vague prompts like "something nice" or overly complex prompts + with contradicting elements. Do not list too many subjects as this + confuses the generation. +

+

+ Another common mistake is not iterating. Your first prompt rarely + produces the perfect result - refine and adjust. +

+ + + Advanced Techniques + +

+ As you gain experience, try techniques like negative prompting + (specifying what you do not want), weighted terms, and compositional + guidance. +

+

+ Keep a prompt journal to track what works for your specific use cases. +

+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/_posts/use-cases-ecommerce.tsx b/apps/landing/src/app/(landings)/blog/_posts/use-cases-ecommerce.tsx new file mode 100644 index 0000000..e7b6a56 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_posts/use-cases-ecommerce.tsx @@ -0,0 +1,109 @@ +import { BlogHeading, BlogQuote, BlogCTA, BlogCodeBlock } from '../_components'; +import type { TocItem } from '../types'; + +export const tocItems: TocItem[] = [ + { id: 'overview', text: 'Overview', level: 2 }, + { id: 'product-mockups', text: 'Product Mockups', level: 2 }, + { id: 'category-banners', text: 'Category Banners', level: 2 }, + { id: 'lifestyle-images', text: 'Lifestyle Images', level: 3 }, + { id: 'ab-testing', text: 'A/B Testing Visuals', level: 2 }, + { id: 'implementation', text: 'Implementation Example', level: 2 }, +]; + +export const Content = () => ( + <> + + Overview + +

+ E-commerce platforms face unique challenges when it comes to visual + content. From product listings to marketing banners, the need for + high-quality images is constant and demanding. +

+

+ AI-generated placeholders offer e-commerce teams a way to prototype, + test, and iterate faster than ever before. +

+ + + Product Mockups + +

+ Before product photography is complete, use AI to generate realistic + product mockups. This allows the development and marketing teams to + work in parallel rather than waiting for final assets. +

+ + + We launched our new collection page two weeks earlier by using AI + placeholders during development. Real photos were swapped in seamlessly + when ready. + + + + Category Banners + +

+ Category and promotional banners can be prototyped instantly. Test + different visual themes before committing to professional photo shoots. +

+

+ This is especially valuable for seasonal campaigns where timing is + critical. +

+ + + Lifestyle Images + +

+ Lifestyle images showing products in use are expensive to produce. + AI can generate concept images to validate ideas before investing + in professional shoots. +

+ + + A/B Testing Visuals + +

+ Test different visual styles with real users before full production. + Generate multiple variations of hero images and measure which performs + better. +

+

+ This data-driven approach to visual design can significantly improve + conversion rates. +

+ + + Implementation Example + +

+ Here is a simple example of using Banatie for product category images: +

+ + + {` +
+ Summer Collection +
+ + +
+ Product +
`} +
+ + + +); diff --git a/apps/landing/src/app/(landings)/blog/blog-posts.json b/apps/landing/src/app/(landings)/blog/blog-posts.json index a1e6bea..35d6e13 100644 --- a/apps/landing/src/app/(landings)/blog/blog-posts.json +++ b/apps/landing/src/app/(landings)/blog/blog-posts.json @@ -12,11 +12,101 @@ "avatar": "/blog/authors/default.jpg" }, "readTime": "5 min", - "relatedArticles": [], + "relatedArticles": ["api-integration-tips", "prompt-engineering-basics"], "relatedDocs": [ { "title": "API Reference", "href": "/docs/api/", "icon": "code" }, { "title": "Quick Start", "href": "/docs/guides/", "icon": "book" } ] + }, + { + "slug": "api-integration-tips", + "title": "API Integration Tips for Developers", + "description": "Best practices for integrating Banatie API into your applications and workflows.", + "heroImage": "/blog/api-tips-hero.jpg", + "category": "tutorials", + "date": "2025-01-12", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "7 min", + "relatedArticles": ["placeholder-images-guide", "image-generation-best-practices"], + "relatedDocs": [ + { "title": "API Reference", "href": "/docs/api/", "icon": "code" }, + { "title": "Authentication", "href": "/docs/guides/", "icon": "book" } + ] + }, + { + "slug": "design-workflow-optimization", + "title": "Optimizing Your Design Workflow with AI Images", + "description": "How designers can speed up their prototyping process using AI-generated placeholders.", + "heroImage": "/blog/design-workflow-hero.jpg", + "category": "guides", + "date": "2025-01-10", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "6 min", + "relatedArticles": ["use-cases-ecommerce", "placeholder-images-guide"], + "relatedDocs": [ + { "title": "Getting Started", "href": "/docs/guides/", "icon": "book" }, + { "title": "Image Parameters", "href": "/docs/api/", "icon": "code" } + ] + }, + { + "slug": "prompt-engineering-basics", + "title": "Prompt Engineering for Better Image Results", + "description": "Master the art of writing prompts that generate exactly the images you need.", + "heroImage": "/blog/prompt-engineering-hero.jpg", + "category": "tutorials", + "date": "2025-01-08", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "8 min", + "relatedArticles": ["image-generation-best-practices", "placeholder-images-guide"], + "relatedDocs": [ + { "title": "Prompt Guide", "href": "/docs/guides/", "icon": "book" }, + { "title": "API Reference", "href": "/docs/api/", "icon": "code" } + ] + }, + { + "slug": "image-generation-best-practices", + "title": "Best Practices for AI Image Generation", + "description": "Learn the do's and don'ts of generating high-quality AI images for your projects.", + "heroImage": "/blog/best-practices-hero.jpg", + "category": "guides", + "date": "2025-01-05", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "6 min", + "relatedArticles": ["prompt-engineering-basics", "api-integration-tips"], + "relatedDocs": [ + { "title": "Image Quality", "href": "/docs/guides/", "icon": "book" }, + { "title": "Parameters", "href": "/docs/api/", "icon": "code" } + ] + }, + { + "slug": "use-cases-ecommerce", + "title": "AI Placeholders for E-commerce Projects", + "description": "How online stores can use AI-generated images for product mockups and prototypes.", + "heroImage": "/blog/ecommerce-hero.jpg", + "category": "use-cases", + "date": "2025-01-02", + "author": { + "name": "Banatie Team", + "avatar": "/blog/authors/default.jpg" + }, + "readTime": "5 min", + "relatedArticles": ["design-workflow-optimization", "image-generation-best-practices"], + "relatedDocs": [ + { "title": "Quick Start", "href": "/docs/guides/", "icon": "book" }, + { "title": "Batch Generation", "href": "/docs/api/", "icon": "code" } + ] } ] } From 38b377496fb68032849bf2c0d6ccd3745328866f Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sun, 18 Jan 2026 00:43:15 +0700 Subject: [PATCH 04/13] feat: apply design for block article --- .../src/app/(landings)/blog/[slug]/page.tsx | 39 +++---- .../blog/_components/BlogCodeBlock.tsx | 56 ++++++++-- .../blog/_components/BlogHeading.tsx | 2 +- .../(landings)/blog/_components/BlogImage.tsx | 19 +++- .../blog/_components/BlogInfoBox.tsx | 55 +++++++++ .../blog/_components/BlogLeadParagraph.tsx | 11 ++ .../blog/_components/BlogPostHeader.tsx | 101 ++++++++++++----- .../(landings)/blog/_components/BlogQuote.tsx | 8 +- .../blog/_components/BlogShareButtons.tsx | 60 ++++++++++ .../blog/_components/BlogSidebar.tsx | 105 +++++++++++------- .../(landings)/blog/_components/BlogTOC.tsx | 45 ++++---- .../app/(landings)/blog/_components/index.ts | 3 + apps/landing/src/app/globals.css | 20 ++++ 13 files changed, 390 insertions(+), 134 deletions(-) create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogInfoBox.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogLeadParagraph.tsx create mode 100644 apps/landing/src/app/(landings)/blog/_components/BlogShareButtons.tsx diff --git a/apps/landing/src/app/(landings)/blog/[slug]/page.tsx b/apps/landing/src/app/(landings)/blog/[slug]/page.tsx index b4ee8a4..8b59b01 100644 --- a/apps/landing/src/app/(landings)/blog/[slug]/page.tsx +++ b/apps/landing/src/app/(landings)/blog/[slug]/page.tsx @@ -8,9 +8,9 @@ import { } from '../utils'; import { BlogPostHeader, - BlogBreadcrumbs, BlogTOC, BlogSidebar, + BlogShareButtons, } from '../_components'; interface PageProps { @@ -42,29 +42,26 @@ export default async function BlogPostPage({ params }: PageProps) { return (
-
-
- -
-
- -
-
-
-
- -
+
+
+
+ {/* Share buttons column - hidden on mobile */} + -