VisionaryAI’s frontend is built with Next.js 13 using the App Router, React Server Components, and modern client-side patterns.
Project structure
The frontend follows Next.js 13 App Router conventions:
app/
├── api/ # API Routes (middleware layer)
│ ├── generateImage/
│ │ └── route.ts
│ ├── getImages/
│ │ └── route.ts
│ └── suggestion/
│ └── route.ts
├── layout.tsx # Root layout with metadata
└── page.tsx # Home page
components/
├── ClientProvider.tsx # Toast notification provider
├── Header.tsx # App header (Server Component)
├── Images.tsx # Image gallery (Client Component)
└── PromptInput.tsx # Prompt form (Client Component)
lib/
├── fetchImages.ts # SWR fetcher for images
└── fetchSuggestionFromChatGPT.ts # SWR fetcher for suggestions
Core components
Layout component
The root layout defines metadata and wraps the entire application:
import "../styles/globals.css";
import Header from "@/components/Header";
import PromptInput from "@/components/PromptInput";
import ClientProvider from "../components/ClientProvider";
import { Analytics } from "@vercel/analytics/react";
export const metadata = {
title: "VisionaryAI - AI image generator",
description: "AI image generator Powered by Next.js, DALL-E & ChatGPT",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ClientProvider>
<Header />
<PromptInput />
{children}
<Analytics />
</ClientProvider>
</body>
</html>
);
}
The layout uses React Server Components by default, providing automatic code splitting and reduced client-side JavaScript.
A server component that renders the application header:
import Image from "next/image";
import Link from "next/link";
function Header() {
return (
<header className="flex justify-between p-4 bg-white z-40 sticky shadow-md top-0">
<div className="flex space-x-2">
<Image
src="https://seeklogo.com/images/O/open-ai-logo-8B9BFEDC26-seeklogo.com.png"
alt="VisionaryAI"
width={37}
height={20}
/>
<div>
<h1 className="font-bold">
VisionaryAI: <span className="text-violet-500">AI</span> Image
generator
</h1>
<h2 className="text-xs">Powered by DALL.E 3 & GPT turbo</h2>
</div>
</div>
<div className="flex divide-x text-xs md:text-base items-center text-gray-500">
<Link href="https://twitter.com/Srijan_Dby" className="px-2 font-light">
Contact Me!
</Link>
<Link
href="https://github.com/Srijan-D/DALLE3"
className="px-2 font-light"
>
Github repository
</Link>
</div>
</header>
);
}
export default Header;
The header uses Next.js Image component for automatic image optimization and Link for client-side navigation.
A client component that handles user input and AI prompt suggestions:
"use client";
import fetchSuggestionFromChatGPT from "@/lib/fetchSuggestionFromChatGPT";
import fetchImages from "@/lib/fetchImages";
import { useState } from "react";
import useSWR from "swr";
import toast from "react-hot-toast";
function PromptInput() {
const [input, setInput] = useState("");
// Fetch AI-powered prompt suggestion
const {
data: suggestion,
error,
isLoading,
mutate,
isValidating,
} = useSWR("/api/suggestion", fetchSuggestionFromChatGPT, {
revalidateOnFocus: false,
});
// Access to image list mutator
const { mutate: updateImages } = useSWR("images", fetchImages, {
revalidateOnFocus: false,
});
const loading = isLoading || isValidating;
const submitPrompt = async (useSuggestion?: boolean) => {
const inputPrompt = input;
setInput("");
const p = useSuggestion ? suggestion : inputPrompt;
const notificationPrompt = p.length > 20 ? `${p.slice(0, 20)}...` : p;
const notification = toast.loading(
`DALLE3 is generating an image for "${notificationPrompt}"`
);
const res = await fetch("/api/generateImage", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: p }),
});
const data = await res.json();
if (data.error) {
toast.error(data.error, { id: notification });
} else {
toast.success(`Your AI image has been generated!`, { id: notification });
}
updateImages();
};
// Form submission and button handlers omitted for brevity
}
Key features of PromptInput
Images component
A client component that displays the generated image gallery:
"use client";
import Image from "next/image";
import useSWR from "swr";
import fetchImages from "../lib/fetchImages";
type ImageType = {
name: string;
url: string;
};
function Images() {
const {
data: images,
isLoading,
mutate: refreshImages,
isValidating,
} = useSWR("images", fetchImages, {
revalidateOnFocus: false,
});
return (
<div>
<button
onClick={() => refreshImages(images)}
className="fixed bottom-10 right-10 bg-violet-400/90 text-white px-5 py-3 rounded-md hover:bg-violet-500 focus:outline-none focus:ring-2 font-bold z-20"
>
{!isLoading && isValidating ? "Refreshing..." : "Refresh Images"}
</button>
{isLoading && (
<p className="text-center pb-8 font-extralight">
Loading <span className="text-violet-400">AI</span> Generated
Images...
</p>
)}
<div className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 px-0 md:px-10">
{images?.imageUrls?.map((image: ImageType, num: Number) => (
<div
key={image.name}
className={`relative cursor-help
${num === 0 && "md:col-span-2 md:row-span-2"}
hover:scale-[104%] transition transform duration-300 ease-in-out
`}
>
<div className="absolute flex justify-center items-center w-full h-full bg-white opacity-0 hover:opacity-80 transition-opacity duration-200 z-10">
<p className="text-center font-light text-lg p-5">
{image.name.split("_").shift()?.toString().split(".").shift()}
</p>
</div>
<Image
unoptimized={true}
src={image.url}
alt={image.name}
width={500}
height={500}
className="w-full rounded-sm shadow-2xl drop-shadow-lg z-10"
/>
</div>
))}
</div>
</div>
);
}
The first image in the grid spans 2 columns and 2 rows on medium+ screens, creating an engaging Pinterest-style layout.
State management with SWR
VisionaryAI uses SWR for efficient data fetching and state management:
Fetcher functions
// lib/fetchImages.ts
const fetchImages = () =>
fetch("/api/getImages", {
cache: 'no-store',
}).then(res => res.json())
export default fetchImages;
// lib/fetchSuggestionFromChatGPT.ts
const fetchSuggestionFromChatGPT = () =>
fetch("/api/suggestion", {
cache: 'no-store',
}).then(res => res.json())
export default fetchSuggestionFromChatGPT;
SWR provides automatic revalidation, caching, and optimistic updates without complex state management libraries.
SWR configuration
All SWR hooks use revalidateOnFocus: false to prevent unnecessary refetching when users switch browser tabs:
useSWR("/api/suggestion", fetchSuggestionFromChatGPT, {
revalidateOnFocus: false,
});
Styling with Tailwind CSS
The application uses Tailwind CSS for styling with responsive design patterns:
Responsive grid
Hover effects
Conditional styling
<div className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{/* Images */}
</div>
- Mobile: 1 column
- Tablet: 2 columns
- Desktop: 3 columns
- Large desktop: 4 columns
className="hover:scale-[104%] transition transform duration-300 ease-in-out"
Images scale up by 4% on hover with smooth transitions.className={`relative cursor-help
${num === 0 && "md:col-span-2 md:row-span-2"}
`}
First image spans multiple grid cells on larger screens.
Client-side notifications
The ClientProvider component wraps the app with react-hot-toast:
"use client"
import { Toaster } from "react-hot-toast"
export default function ClientProvider({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<Toaster position="bottom-center"/>
{children}
</>
)
}
Toast notifications provide non-intrusive feedback for image generation status, errors, and success messages.
API routes (middleware)
Next.js API routes act as a secure proxy layer:
Generate image route
// app/api/generateImage/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const res = await request.json();
const prompt = res.prompt;
const response = await fetch(
"https://ai-imagegenerator.azurewebsites.net/api/generateImage?",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt }),
}
);
const textData = await response.text();
return NextResponse.json({ textData });
}
Get images route
// app/api/getImages/route.ts
export async function GET(request: Request) {
const response = await fetch(
"https://ai-imagegenerator.azurewebsites.net/api/getImages?",
{
cache: "no-store",
}
);
const blob = await response.blob();
const textData = await blob.text();
const data = JSON.parse(textData);
return new Response(JSON.stringify(data), {
status: 200,
});
}
Suggestion route
// app/api/suggestion/route.ts
export async function GET(request: Request) {
const response = await fetch(
"https://ai-imagegenerator.azurewebsites.net/api/getChatGPTSuggestion?",
{
cache: "no-store",
}
);
const textData = await response.text();
return new Response(JSON.stringify(textData.trim()), {
status: 200,
});
}
API routes use cache: "no-store" to ensure fresh data on every request, critical for real-time image generation.
The Header component is a Server Component, reducing client-side JavaScript and improving initial page load.
Next.js 13 App Router automatically code-splits routes and components, loading only necessary JavaScript.
SWR caches API responses, reducing redundant network requests and improving perceived performance.
The Images component uses optimistic updates when refreshing, maintaining UI responsiveness.
Next steps
Backend development
Learn about Azure Functions and OpenAI integration
Local setup
Set up the development environment on your machine