Skip to main content
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.

Header component

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.

PromptInput component

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
}
  • AI suggestions: Uses ChatGPT to generate creative prompt ideas
  • Optimistic updates: Shows loading states while waiting for API responses
  • Toast notifications: Provides real-time feedback on generation status
  • Dual submit modes: Users can submit their own prompt or use AI suggestion

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:
<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

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.

Performance optimizations

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

Build docs developers (and LLMs) love