Today I'll build a complete full-stack application with a React frontend and Python backend, all deployed to production on Vercel.
A Business Idea Generator - an AI-powered SaaS application that:
- Has a modern React frontend built with Next.js (using Pages Router for stability)
- Uses TypeScript for type safety
- Connects to a FastAPI backend
- Streams AI responses in real-time
- Renders beautiful Markdown content
- Deploys seamlessly to production
- Completed Day 1 (you should have Node.js and Vercel CLI installed)
- Your OpenAI API key from Day 1
- Open Cursor
- Open the terminal (Terminal → New Terminal or Ctrl+` / Cmd+`)
- Navigate to your projects folder (or wherever you want to create the project)
- Create a new Next.js project with TypeScript:
npx create-next-app saas --typescriptWhen prompted, respond to each question:
- Which linter would you like to use? → Press Enter for ESLint (default)
- Would you like to use Tailwind CSS? → Type
yand press Enter for Yes - Would you like your code inside a
src/directory? → Typenand press Enter for No - Would you like to use App Router? (recommended) → Type
nand press Enter for No (we're using Pages Router) - Would you like to use Turbopack? (recommended) → Type
nand press Enter for No (we'll keep the standard build for compatibility) - Would you like to customize the import alias? → Type
nand press Enter for No
This creates a new Next.js project with:
- Pages Router (the stable, battle-tested routing system)
- TypeScript for type safety
- ESLint for catching errors and enforcing code quality
- Tailwind CSS for utility-first styling
- In Cursor: File → Open Folder → Select the "saas" folder that was just created
- You'll see several files and folders that Next.js created automatically
Next.js created these key files and folders:
saas/
├── pages/ # Pages Router directory (where your pages live)
│ ├── _app.tsx # Application wrapper (initializes pages)
│ ├── _document.tsx # Custom document (HTML structure)
│ ├── index.tsx # Homepage (routes to "/")
│ └── api/ # API routes directory (we'll remove this)
│ └── hello.ts # Sample API route (we'll remove this)
├── styles/ # Styles directory
│ └── globals.css # Global styles (includes Tailwind)
├── public/ # Static files (images, fonts, etc.)
├── package.json # Node.js dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── next.config.js # Next.js configuration
└── node_modules/ # Installed packages (auto-generated)
Key files explained:
pages/_app.tsx: The application wrapper that initializes all pages. Used for global providers and stylespages/_document.tsx: Custom document for modifying the HTML structurepages/index.tsx: Your homepage component. This is what users see at "/"styles/globals.css: Global styles including Tailwind CSS imports
Since we're using a Python FastAPI backend (not Next.js API routes), let's remove the sample API directory:
- In Cursor's file explorer (left sidebar), find the
pages/apifolder - Right-click on the
apifolder - Select Delete (or press Delete/Backspace key)
- Confirm the deletion when prompted
Tailwind CSS is a utility-first CSS framework. Instead of writing custom CSS, you apply pre-built utility classes directly in your HTML/JSX. For example:
bg-blue-500sets a blue backgroundtext-whitemakes text whitep-4adds padding on all sidesrounded-lgrounds the corners
This approach makes styling faster and more consistent!
In Cursor's file explorer, create a new folder at the root level:
- Right-click in the file explorer → New Folder → name it
api
Create a new file requirements.txt in the root directory with:
fastapi
uvicorn
openai
Create a new file api/index.py:
from fastapi import FastAPI # type: ignore
from fastapi.responses import PlainTextResponse # type: ignore
from openai import OpenAI # type: ignore
app = FastAPI()
@app.get("/api", response_class=PlainTextResponse)
def idea():
client = OpenAI()
prompt = [{"role": "user", "content": "Come up with a new business idea for AI Agents"}]
response = client.chat.completions.create(model="gpt-5-nano", messages=prompt)
return response.choices[0].message.contentIn Next.js Pages Router, all page components run on both server and client by default. Since we're using a Python/FastAPI backend for our API (not Next.js's server), we'll mark our components with "use client" to ensure:
- The component runs in the browser
- The browser makes direct API calls to our Python backend
- We're not trying to use Next.js as a middleman server
Replace the entire contents of pages/index.tsx with:
"use client"
import { useEffect, useState } from 'react';
export default function Home() {
const [idea, setIdea] = useState<string>('…loading');
useEffect(() => {
fetch('/api')
.then(res => res.text())
.then(setIdea)
.catch(err => setIdea('Error: ' + err.message));
}, []);
return (
<main className="p-8 font-sans">
<h1 className="text-3xl font-bold mb-4">
Business Idea Generator
</h1>
<div className="w-full max-w-2xl p-6 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm">
<p className="text-gray-900 dark:text-gray-100 whitespace-pre-wrap">
{idea}
</p>
</div>
</main>
);
}What's happening here:
"use client"tells Next.js this component runs in the browser- The browser directly calls our Python FastAPI backend at
/api - We use React hooks to manage the UI state and fetch the data
- Vercel routes
/apirequests to our Python server (we don't need vercel.json configuration)
The _app.tsx file wraps all your pages. Let's create it to import our styles.
Create or replace pages/_app.tsx with:
import type { AppProps } from 'next/app';
import '../styles/globals.css'; // This imports Tailwind styles
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}Now let's customize the HTML structure and add metadata.
Create pages/_document.tsx:
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
<title>Business Idea Generator</title>
<meta name="description" content="AI-powered business idea generation" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}Note: We don't need a vercel.json file - Vercel automatically detects both Next.js and Python files in the api folder using its default configuration.
First, let's create and link your Vercel project:
vercel linkFollow the prompts:
- Set up and link? → Yes
- Which scope? → Your personal account
- Link to existing project? → No
- What's the name of your project? → saas
- In which directory is your code located? → Current directory (press Enter)
This creates your Vercel project and links it to your local directory.
Now that the project is created, add your OpenAI API key:
vercel env add OPENAI_API_KEY- Paste your API key when prompted
- Select all environments (development, preview, production)
Deploy your application to test it:
vercel .When prompted "Set up and deploy?", answer No (we already linked the project).
Visit the URL provided to see your Business Idea Generator loading an AI-generated idea!
Note: We test using the deployed version rather than local development, as this ensures both the Next.js frontend and Python backend work together properly.
Deploy your working application to production:
vercel --prodVisit the URL provided to see your live application!
Now let's enhance your app with real-time streaming and Markdown rendering.
npm install react-markdown remark-gfm remark-breaksReplace pages/index.tsx with:
"use client"
import { useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import remarkBreaks from 'remark-breaks';
export default function Home() {
const [idea, setIdea] = useState<string>('…loading');
useEffect(() => {
const evt = new EventSource('/api');
let buffer = '';
evt.onmessage = (e) => {
buffer += e.data;
setIdea(buffer);
};
evt.onerror = () => {
console.error('SSE error, closing');
evt.close();
};
return () => { evt.close(); };
}, []);
return (
<main className="p-8 font-sans">
<h1 className="text-3xl font-bold mb-4">
Business Idea Generator
</h1>
<div className="w-full max-w-2xl p-6 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-md">
<div className="prose prose-gray dark:prose-invert max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
>
{idea}
</ReactMarkdown>
</div>
</div>
</main>
);
}Tailwind classes explained:
prose: Tailwind Typography plugin class that styles markdown content beautifullyw-full max-w-2xl: Full width with a maximum width constraintp-6: Padding on all sidesbg-gray-50: Light gray backgroundborder border-gray-200: Border with gray colorrounded-lg: Rounded corners
Note: We still need "use client" at the top because we're making direct API calls from the browser to our Python FastAPI backend (rather than using Next.js as a middleman server).
The prose class requires the Typography plugin. Install it:
npm install @tailwindcss/typographyReplace api/index.py with:
from fastapi import FastAPI # type: ignore
from fastapi.responses import StreamingResponse # type: ignore
from openai import OpenAI # type: ignore
app = FastAPI()
@app.get("/api")
def idea():
client = OpenAI()
prompt = [{"role": "user", "content": "Come up with a new business idea for AI Agents"}]
stream = client.chat.completions.create(model="gpt-5-nano", messages=prompt, stream=True)
def event_stream():
for chunk in stream:
text = chunk.choices[0].delta.content
if text:
lines = text.split("\n")
for line in lines:
yield f"data: {line}\n"
yield "\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")Deploy and test your updated application:
vercel .Visit the URL provided. You'll now see the AI response streaming in real-time with proper Markdown formatting!
Let's make your app look professional with modern styling.
First, we need to restore the default HTML styles that Tailwind removes. Add this to the bottom of your styles/globals.css file:
@layer base {
.markdown-content h1 {
font-size: 2em;
font-weight: bold;
margin: 0.67em 0;
}
.markdown-content h2 {
font-size: 1.5em;
font-weight: bold;
margin: 0.83em 0;
}
.markdown-content h3 {
font-size: 1.17em;
font-weight: bold;
margin: 1em 0;
}
.markdown-content h4 {
font-size: 1em;
font-weight: bold;
margin: 1.33em 0;
}
.markdown-content h5 {
font-size: 0.83em;
font-weight: bold;
margin: 1.67em 0;
}
.markdown-content h6 {
font-size: 0.67em;
font-weight: bold;
margin: 2.33em 0;
}
.markdown-content p {
margin: 1em 0;
}
.markdown-content ul {
list-style-type: disc;
padding-left: 2em;
margin: 1em 0;
}
.markdown-content ol {
list-style-type: decimal;
padding-left: 2em;
margin: 1em 0;
}
.markdown-content li {
margin: 0.25em 0;
}
.markdown-content strong {
font-weight: bold;
}
.markdown-content em {
font-style: italic;
}
.markdown-content hr {
border: 0;
border-top: 1px solid #e5e7eb;
margin: 2em 0;
}
}Update the prompt in api/index.py to request formatted output:
prompt = [{"role": "user", "content": "Reply with a new business idea for AI Agents, formatted with headings, sub-headings and bullet points"}]Now replace pages/index.tsx with:
"use client"
import { useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import remarkBreaks from 'remark-breaks';
export default function Home() {
const [idea, setIdea] = useState<string>('…loading');
useEffect(() => {
const evt = new EventSource('/api');
let buffer = '';
evt.onmessage = (e) => {
buffer += e.data;
setIdea(buffer);
};
evt.onerror = () => {
console.error('SSE error, closing');
evt.close();
};
return () => { evt.close(); };
}, []);
return (
<main className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
<div className="container mx-auto px-4 py-12">
{/* Header */}
<header className="text-center mb-12">
<h1 className="text-5xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent mb-4">
Business Idea Generator
</h1>
<p className="text-gray-600 dark:text-gray-400 text-lg">
AI-powered innovation at your fingertips
</p>
</header>
{/* Content Card */}
<div className="max-w-3xl mx-auto">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8 backdrop-blur-lg bg-opacity-95">
{idea === '…loading' ? (
<div className="flex items-center justify-center py-12">
<div className="animate-pulse text-gray-400">
Generating your business idea...
</div>
</div>
) : (
<div className="markdown-content text-gray-700 dark:text-gray-300">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkBreaks]}
>
{idea}
</ReactMarkdown>
</div>
)}
</div>
</div>
</div>
</main>
);
}Professional Tailwind styling:
min-h-screen: Full viewport heightbg-gradient-to-br: Beautiful gradient background with dark mode supportcontainer mx-auto: Centered container with responsive paddingtext-5xl font-bold bg-gradient-to-r bg-clip-text text-transparent: Gradient text effect for the headingrounded-2xl shadow-xl backdrop-blur-lg: Modern glassmorphism card effectanimate-pulse: Loading animation while content streamsmarkdown-content: Custom class that restores HTML styling for markdown
Deploy your enhanced application:
vercel --prodYou've built a complete SaaS application with:
- ✅ Modern React frontend with Next.js Pages Router
- ✅ TypeScript for type safety
- ✅ FastAPI Python backend
- ✅ Real-time streaming AI responses
- ✅ Beautiful Markdown rendering
- ✅ Professional styling
- ✅ Production deployment on Vercel
- How to structure a full-stack application
- Building with Next.js Pages Router
- Understanding client-side rendering for API calls
- Creating API endpoints with FastAPI
- Implementing Server-Sent Events for streaming
- Rendering Markdown content in React
- Deploying full-stack apps to Vercel
Pages Router Structure:
- Each file in
pages/becomes a route pages/index.tsx→/pages/product.tsx→/productpages/api/→ API routes (though we're using Python instead)
Client-Side Rendering ("use client"):
- Components marked with
"use client"run primarily in the browser - Can use React hooks (useState, useEffect)
- Perfect for dynamic, interactive UI
- We use this for all our pages since we're calling a Python backend
In this project, we used client-side components because we needed browser features for real-time streaming and connecting to our FastAPI backend.
- Add a button to generate new ideas
- Store ideas in a database
- Add user authentication
- Create different idea categories
- Add a copy-to-clipboard feature
- Implement idea saving and sharing
- Make sure you've installed all npm packages
- Try deleting
node_modulesand runningnpm installagain
- Check that your OpenAI API key is set correctly
- Verify you have credits in your OpenAI account
- Some browsers block SSE on localhost - try a different browser
- Check the browser console for errors
- ESLint helps catch potential issues in your code
- Yellow squiggly lines are warnings (code will still run)
- Red squiggly lines are errors (should be fixed)
- You can temporarily disable ESLint for a line with
// eslint-disable-next-line - Common warnings:
- "React Hook useEffect has missing dependencies" - Usually safe to ignore for simple demos
- "Unused variable" - Remove variables you're not using
- Ensure all TypeScript packages are installed
- Restart your development server after installing types
- Make sure all files are saved before deploying
- Check that vercel.json is properly formatted
- Ensure your API key is added to Vercel environment variables
Successful deployment to production - This screenshot demonstrates the complete Git workflow and successful deployment to Vercel. It shows:
- All files committed and pushed to the GitHub repository (Ike-DevCloudIQ/SaaS)
- Vercel's automatic deployment triggered from the Git push
- Production URL generated and live at
https://saas-4p6xjwwb8-ikenna-ubahs-projects.vercel.app - The full-stack application deployed with both Next.js frontend and Python FastAPI backend running seamlessly on Vercel's platform
AI-powered Business Idea Generator in action - This screenshot showcases the completed SaaS application generating real-time AI business ideas. Key features demonstrated:
- Clean, modern UI built with Next.js, TypeScript, and Tailwind CSS
- Real-time AI response streaming from OpenAI's GPT model
- Beautiful Markdown rendering with the Tailwind Typography plugin
- Full-stack integration: React frontend communicating with FastAPI backend
- Production-ready application generating detailed business ideas including value propositions and core features
- Example output: "FlowAgent" - an AI-powered no-code platform for SMB automation, showcasing the app's ability to generate comprehensive, market-ready business concepts

