This is my first time using Lovable to build a frontend for a Strapi backend. Here's what I learned.
AI tools like Lovable.dev and Claude Code help you bring ideas to life faster. They're great for quick prototypes and trying new approaches. But they're not magic—they're tools.
Like any tool, they have strengths and weaknesses. Success comes from knowing what each tool does well and using it appropriately.
You still need to understand what you're building. You need to know your data structure, the problems you're solving, and your tech stack. And yes—you still need to know how to code.
These tools are powerful, but they sometimes hallucinate or create errors. The better you understand your project structure and how AI tools work, the better your results will be.
In this post, I'll walk you through building a frontend with Lovable.dev that connects to a Strapi backend.
What This Covers: Using Lovable.dev (AI coding tool) to build a frontend for your Strapi backend
Key Insights:
Critical Success Factors:
Common Mistakes: 1. Vague prompts → Generic code 2. Missing version info → Wrong Strapi patterns 3. No real data examples → Incorrect field names
Lovable.dev is a browser-based AI development platform. You describe what you want in plain English, and Lovable writes the code for you.
Vibe coding is a relaxed, creative way to build applications using AI tools. Instead of writing every line of code yourself, you describe what you want and let the AI handle the heavy lifting. Then you guide and refine the work step by step.
The "vibe" is about maintaining momentum—trying ideas quickly without getting stuck on setup or syntax.
These tools have their place, especially when you want to prototype or iterate on initial ideas quickly.
Here's why this combination works:
Your rubber ducky (that codes) Talk to Lovable like you'd talk through a problem with a teammate. It helps you think out loud, propose structures, and generate a first pass so you're not staring at a blank file.
Quick iteration loops Change a prompt, regenerate, test against your Strapi API, repeat. It's great for exploring layouts, components, and flows without hand-wiring every detail upfront.
Fast POCs and trying new ideas Need a demo by this afternoon? Lovable can scaffold the UI and basic logic while Strapi gives you real content types, draft/publish workflow, and an API you can trust.
Boilerplate handled Forms, lists, detail pages, basic CRUD—Lovable generates the scaffolding so you can focus on the parts that actually need your expertise.
Content-first development With Strapi defining the schema and relationships, you can point Lovable at the API and have it build around your real data shape instead of lorem ipsum.
When building an application that manages content, you have three main approaches:
For this tutorial, we're starting with the backend using Strapi. Here's why:
Even with these tradeoffs, starting with your data first (using Strapi) is the most practical approach.
OpenAPI Specification (formerly Swagger) is a standard way to describe how your API works. Think of it as a detailed instruction manual that explains:
Here's a simple example:
1openapi: 3.0.0
2paths:
3 /api/articles:
4 get:
5 summary: Get all articles
6 responses:
7 "200":
8 description: Success
9 content:
10 application/json:
11 schema:
12 type: object
13 properties:
14 data:
15 type: array
16 items:
17 $ref: "#/components/schemas/Article"
18components:
19 schemas:
20 Article:
21 type: object
22 properties:
23 id:
24 type: integer
25 title:
26 type: string
27 content:
28 type: stringOpenAPI provides a way to document your API that makes it a useful reference when prompting AI tools.
One limitation: Lovable has a context window, and if your specification.json file is too large, it may not fit. But you can still add it as an attachment.
Either way, it's a valuable reference worth including.
How it helps:
Strapi makes this simple with one command:
# Generate OpenAPI spec
npx strapi openapi generate
# This creates an specification.json file in your project rootThis file contains everything about your API—all your content types, components, and endpoints with their exact data structures. It's exactly what AI tools like Lovable.dev need to understand your backend.
Pro tip: I use the OpenAPI extension in VS Code to browse through the endpoints visually.
We'll explore how to use this specification in detail as we go through the tutorial.
Now we'll walk through building a frontend with Lovable.dev that connects to a Strapi backend.
Let's check what you need:
Don't have a Strapi project yet? No problem. We'll use a pre-built one from my Strapi Crash Course. If you want to learn how the backend was created, check out the video tutorial.
git clone https://github.com/PaulBratslavsky/pauls-strapi-crashcourse serverChange into the server folder:
cd serverInstall all the dependencies using either yarn or npm:
yarnor
npm installCreate a .env file in the root of the project. Copy the configuration from the .env.example file:
HOST=0.0.0.0
PORT=1337
APP_KEYS="toBeModified1,toBeModified2"
API_TOKEN_SALT=tobemodified
ADMIN_JWT_SECRET=tobemodified
TRANSFER_TOKEN_SALT=tobemodified
JWT_SECRET=tobemodified
ENCRYPTION_KEY=tobemodifiedReplace each placeholder string with a unique value of your choice.
Seed the project with initial data:
yarn strapi import -f ./seed-data.tar.gzWhen prompted, type y for Yes:
➜ server git:(main) yarn strapi import -f ./seed-data.tar.gz
yarn run v1.22.22
$ strapi import -f ./seed-data.tar.gz
? The import will delete your existing data! Are you sure you want to proceed? (y/N) yType y again:
[2025-10-24 10:09:33.381] warn: (Schema Integrity) admin::session does not exist on source
? There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want
to continue? (y/N)This is just a warning about a schema mismatch. It's safe to ignore.
Once the import finishes, you should see:
Starting import...
✔ entities: 55 transferred (size: 46.7 KB) (elapsed: 64 ms) (729.6 KB/s)
✔ assets: 8 transferred (size: 300 KB) (elapsed: 21 ms) (13.9 MB/s)
✔ links: 171 transferred (size: 32.2 KB) (elapsed: 11 ms) (2.9 MB/s)
✔ configuration: 47 transferred (size: 135.4 KB) (elapsed: 8 ms) (16.5 MB/s)
┌─────────────────────────────────────────┬───────┬───────────────┐
│ Type │ Count │ Size │
├─────────────────────────────────────────┼───────┼───────────────┤
│ entities │ 55 │ 46.7 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::article.article │ 12 │ ( 22.3 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::author.author │ 2 │ ( 534 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::global.global │ 2 │ ( 3.3 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::landing-page.landing-page │ 2 │ ( 7.9 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::page.page │ 4 │ ( 1.6 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::tag.tag │ 3 │ ( 820 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::i18n.locale │ 1 │ ( 253 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::upload.file │ 8 │ ( 4.1 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::upload.folder │ 2 │ ( 519 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.permission │ 16 │ ( 4.4 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.role │ 2 │ ( 656 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.user │ 1 │ ( 464 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ assets │ 8 │ 300 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- .avif │ 3 │ ( 292.1 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- .svg │ 5 │ ( 7.9 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ links │ 171 │ 32.2 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ configuration │ 47 │ 135.4 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ Total │ 281 │ 514.2 KB │
└─────────────────────────────────────────┴───────┴───────────────┘
Import process has been completed successfully!
✨ Done in 15.24s.Start your Strapi project:
yarn devNavigate to http://localhost:1337
You'll see the admin login screen. Create your first Admin user:
After creating your user, you'll see the Strapi dashboard:
Great! Now let's look at what content types we have available.
Here's how the articles content type works and how to get its data from the API:
The Strapi backend has several content types in /server/src/api. For this tutorial, we'll build three main pages:
Here are the main content types:
Global Content Type (api::global.global):
Article Content Type (api::article.article):
Landing Page Content Type (api::landing-page.landing-page):
Author Content Type (api::author.author):
Tag Content Type (api::tag.tag):
The seed data we imported already has public access configured, so anyone can read your content without authentication. If you're building your own Strapi project from scratch, you'll need to enable this yourself.
Here's how:
find and findOneWe'll work with four main endpoints:
Global Settings (Single Type):
GET http://localhost:1337/api/global
Gets your site-wide settings like header navigation, footer, and banner. You'll use this data on every page.
Landing Page (Single Type):
GET http://localhost:1337/api/landing-page
Gets your homepage content with all its customizable blocks.
Articles List (Collection Type):
GET http://localhost:1337/api/articles
Gets all your published articles with their authors, tags, and images.
Single Article (Collection Type):
GET http://localhost:1337/api/articles?filters[slug][$eq]=why-java-script-is-still-the-most-popular-programming-language
Gets one specific article by its slug (the URL-friendly version of the title).
Page By Slug (Collection Type):
GET http://localhost:1337/api/pages?filters[slug][$eq]=about
Gets one specific page by its slug. Pages use the same block system as the landing page, allowing you to create dynamic content pages like About, Contact, etc.
Things to note:
filters[slug][$eq] part is how Strapi filters data by specific field values1337 (Strapi's default)Now that your content types are set up, let's create the OpenAPI specification file:
# In your Strapi project directory
yarn strapi openapi generateThis creates a specification.json file with all your API details. Want to browse it visually? Install the OpenAPI VS Code extension.
While you can keep Strapi running locally, deploying it makes working with Lovable.dev much easier. We'll use Strapi Cloud, which has a free tier.
Want to keep Strapi running on your computer? You can use ngrok to create a temporary public URL:
# Install ngrok first from https://ngrok.com/download
# Then run this command to expose your local Strapi instance
ngrok http 1337You'll get a public URL (like https://abc123.ngrok.io) that Lovable.dev can use to reach your local Strapi.
But I'll show you how to deploy to Strapi Cloud for free. You can choose which path to take.
Deploy to Strapi Cloud (Recommended)
For a more permanent solution, deploy to Strapi Cloud:
Navigate to Strapi Cloud and create an account if you don't already have one:
In your dashboard, click the create project button:
Choose the Free Plan:
Select your account and project:
Name your Cloud project and select your region.
I'll call mine lovable-example and pick US East region:
Click Create Project to deploy:
Once deployment finishes, navigate to your Strapi instance and create your first admin user. Notice we don't have any data yet—we'll add it using the transfer command.
First, create a Transfer Token in Strapi.
Navigate to Setting => Transfer Token and click Add new transfer token:
Create your token:
Now use the CLI to transfer your data to Strapi Cloud.
Run this command in your local server directory:
yarn strapi transferWhen prompted, enter your project URL (in my case it was https://inviting-cheese-ce66d3b9e0.strapiapp.com/admin). Make sure to include the /admin path.
Enter your transfer token, then type y to confirm:
➜ server git:(main) yarn strapi transfer
yarn run v1.22.22
$ strapi transfer
ℹ️ Data transfer documentation: https://docs.strapi.io/dev-docs/data-management/transfer
ℹ️ No transfer configuration found in environment variables
→ Add STRAPI_TRANSFER_URL and STRAPI_TRANSFER_TOKEN environment variables to make the transfer process faster for future runs
? Choose transfer direction: Push local data to remote Strapi
? Enter the URL of the remote Strapi instance to send data to:
https://inviting-cheese-ce66d3b9e0.strapiapp.com/admin
? Enter the transfer token for the remote Strapi destination:
[hidden]
? The transfer will delete existing data from the remote Strapi! Are
you sure you want to proceed? Yes
Starting transfer...
✔ entities: 58 transferred (size: 48.1 KB) (elapsed: 4207 ms) (11.4 KB/s)
✔ assets: 8 transferred (size: 300 KB) (elapsed: 1964 ms) (152.7 KB/s)
✔ links: 174 transferred (size: 32.6 KB) (elapsed: 1987 ms) (16.4 KB/s)
✔ configuration: 48 transferred (size: 141.8 KB) (elapsed: 1318 ms) (107.6 KB/s)
┌─────────────────────────────────────────┬───────┬───────────────┐
│ Type │ Count │ Size │
├─────────────────────────────────────────┼───────┼───────────────┤
│ entities │ 58 │ 48.1 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- admin::session │ 3 │ ( 1.4 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::article.article │ 12 │ ( 22.3 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::author.author │ 2 │ ( 534 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::global.global │ 2 │ ( 3.3 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::landing-page.landing-page │ 2 │ ( 7.8 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::page.page │ 4 │ ( 1.6 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- api::tag.tag │ 3 │ ( 820 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::i18n.locale │ 1 │ ( 253 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::upload.file │ 8 │ ( 4.1 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::upload.folder │ 2 │ ( 519 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.permission │ 16 │ ( 4.4 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.role │ 2 │ ( 656 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- plugin::users-permissions.user │ 1 │ ( 464 B ) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ assets │ 8 │ 300 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- .avif │ 3 │ ( 292.1 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ -- .svg │ 5 │ ( 7.9 KB) │
├─────────────────────────────────────────┼───────┼───────────────┤
│ links │ 174 │ 32.6 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ configuration │ 48 │ 141.8 KB │
├─────────────────────────────────────────┼───────┼───────────────┤
│ Total │ 288 │ 522.5 KB │
└─────────────────────────────────────────┴───────┴───────────────┘
Transfer process has been completed successfully!
✨ Done in 76.51s.
➜ server git:(main)Your data should now appear in your Strapi Cloud instance. Navigate to the api/articles endpoint to verify:
Example: https://inviting-cheese-ce66d3b9e0.strapiapp.com/api/articles
Your API is now live and accessible! Whether you used ngrok or Strapi Cloud, you're ready to build the frontend.
Why did we set up the backend first? Because now Lovable can build your frontend based on real API responses and actual data structures. This means more accurate code and fewer errors.
I'm not a prompting expert, and each platform has its own best practices. Mostly, you should experiment to see what gives you the best results.
While building my project, I tried different prompts—some worked better than others. I'll discuss this more later.
Here's a great guide from Lovable.
Part 1: One-Shot Attempt
You might think: stick all the requirements into one prompt—done!
But unlike a human who can read between the lines, AI can't. You need to provide lots of context, and even then it may not be enough.
I tried one-shot prompting but don't recommend it. We'll still try it for learning purposes, but none of my one-shot attempts produced a working version without follow-up prompts.
However, the better your initial prompt, the fewer follow-ups you'll need.
Why do people try the one-shot approach?
Because it's tempting:
But in reality, it creates more problems than it solves:
We'll walk through BOTH approaches so you can try them yourself.
Part 2: Iterative Approach (Recommended)
After experiencing the limitations of one-shot prompting, I tried building with small, focused prompts:
Why Iterative Prompting Wins:
For a proof-of-concept to show stakeholders, one-shot might work. But for production or anything substantial, incremental prompting is the way to go.
For this post, I want to use the Strapi Client SDK. Since Lovable isn't familiar with it, we'll pass as much context as possible directly in the prompt.
We can also create a knowledge file with that context—we'll try that later with incremental prompting.
Understanding the Strapi Client SDK
Before we write our prompt, let's discuss the @strapi/client SDK—an official package that simplifies working with Strapi APIs.
It provides clean methods for API requests, filtering, pagination, and loading related content.
We'll include this SDK in our Lovable prompt for an important reason: it's relatively new.
Since Lovable's AI may not be familiar with its syntax and implementation patterns, we need to provide complete context.
This is a perfect example of when to be explicit in your prompts—give the AI concrete code examples for tools it might not know well, rather than letting it guess or hallucinate the API.
Basic setup example:
1import { strapi } from '@strapi/client';
2
3// Initialize the Strapi client
4const client = strapi({
5 baseURL: 'https://inviting-cheese-ce66d3b9e0.strapiapp.com',
6 // No authentication needed for public endpoints
7});
8
9// 1. Fetch Global settings (single type)
10const globalData = await client.single('global').find();
11// Returns: { data: { id, documentId, attributes: { title, description, banner, header, footer } } }
12
13// 2. Fetch Landing Page (single type)
14const landingPage = await client.single('landing-page').find();
15// Returns: { data: { id, documentId, attributes: { title, description, blocks: [...] } } }
16
17// 3. Fetch all Articles with pagination
18const articles = await client.collection('articles').find({
19 pagination: { page: 1, pageSize: 6 }
20});
21// Returns: { data: [...], meta: { pagination: { page, pageSize, pageCount, total } } }
22
23// 4. Search articles by title using filters (case-insensitive)
24const searchResults = await client.collection('articles').find({
25 filters: {
26 title: { $containsi: 'javascript' }
27 },
28// Common Strapi v5 filter operators:
29// $eq: equals
30// $ne: not equals
31// $containsi: contains (case-insensitive)
32// $startsWith: starts with
33// $endsWith: ends with
34
35// 5. Fetch single article by slug
36const article = await client.collection('articles').find({
37 filters: {
38 slug: {
39 $eq: 'why-java-script-is-still-the-most-popular-programming-language'
40 }
41 }
42});
43// Returns: { data: [{ id, documentId, attributes: {...} }], meta: {} }
44// Note: Returns array with single item - extract first item: article.data[0]Important Note About Population:
In our Strapi backend, we're already populating all content via a route middleware, so you don't need to add populate parameters in your frontend API calls. The only additional params you'll need are:
Example of our route middleware:
Here's how we configured the articles route to automatically populate all relations:
1// server/src/api/article/routes/article.ts
2import { factories } from "@strapi/strapi";
3
4export default factories.createCoreRouter("api::article.article", {
5 config: {
6 find: {
7 middlewares: ["api::article.article-populate"],
8 },
9 },
10});The populate middleware:
1// server/src/api/article/middlewares/article-populate.ts
2import type { Core } from "@strapi/strapi";
3
4const populate = {
5 featuredImage: {
6 fields: ["url", "alternativeText"],
7 },
8 author: {
9 populate: {
10 image: {
11 fields: ["url", "alternativeText"],
12 },
13 articles: {
14 fields: ["documentId", "title"],
15 },
16 },
17 },
18 contentTags: true,
19 blocks: {
20 on: {
21 "blocks.hero": {
22 populate: {
23 links: true,
24 image: { fields: ["alternativeText", "url"] },
25 },
26 },
27 "blocks.section-heading": true,
28 "blocks.card-grid": { populate: { cards: true } },
29 "blocks.content-with-image": {
30 populate: { link: true, image: { fields: ["alternativeText", "url"] } },
31 },
32 "blocks.markdown": true,
33 "blocks.person-card": {
34 populate: { image: { fields: ["alternativeText", "url"] } },
35 },
36 "blocks.faqs": { populate: { faq: true } },
37 "blocks.newsletter": true,
38 },
39 },
40 relatedArticles: {
41 populate: {
42 featuredImage: { fields: ["alternativeText", "url"] },
43 author: true,
44 },
45 },
46};
47
48export default (config, { strapi }: { strapi: Core.Strapi }) => {
49 return async (ctx, next) => {
50 ctx.query.populate = populate;
51 await next();
52 };
53};This means when you call /api/articles, all relations, images, and dynamic zone blocks are automatically populated—you don't need to add ?populate=deep or specify individual fields.
While we'll include this SDK in our prompt to Lovable, you could also use a simple fetch wrapper or libraries like Axios. The SDK is recommended but not required.
Here is the complete prompt you can paste into Lovable.dev.
I was able to generate this whole project with one prompt. but it took few tries.
But in general you would use incrimental prompting when building your project.
It's massive, but my goal was to give as much context to Lovable as possible using the one-shot approach.
After multiple iterations and testing different approaches, this prompt structure worked best. Here's how each section contributed:
Opening Line (Role Definition)
1You are a senior frontend developer experienced with TypeScript, React, and building dynamic content-driven websites that consume Strapi RESTful APIs.Sets the expertise level and domain.
Task
Single, clear objective: "Build a blog platform frontend that consumes a Strapi v5 backend API."
The "v5" specification matters—it prevents the AI from using outdated Strapi v3/v4 patterns.
Critical Rule: Type-First Development
This became the most important section. The three-step rule forces the AI to: 1. Review actual API response examples first 2. Generate TypeScript interfaces matching EXACT field names 3. NOT assume or guess anything
By being explicit with "MUST" and "DO NOT assume," I tried to eliminate guesswork, but it didn't eliminate it completely. Since LLMs generate statistically likely responses, they're not deterministic by nature.
Backend Configuration
Provides the exact API URL and Strapi v5 response format. The note about "Direct field access (no .attributes nesting)" is crucial—Strapi v5 changed this from v3/v4.
Tech Stack
Lists every library explicitly. No room for the AI to substitute alternatives or use outdated versions.
API Endpoints & Real Response Examples
This is where I went all-in on context. I included 5 complete JSON responses from actual API calls: 1. Global Settings (155 lines) 2. Landing Page with 9 block types (160 lines) 3. Articles (62 lines) 4. Paginated Articles (50 lines) 5. Single Page by Slug (38 lines)
Not sanitized examples—the real data with IDs, timestamps, nulls, nested objects, everything.
Why this mattered: Instead of the AI guessing field names like fullName vs name or image.url vs featuredImage.url, it could see the exact structure. This alone eliminated most potential errors.
Implementation Requirements
Six subsections covering Setup, API Layer, Hooks, Types, Pages, and Development Standards.
The key decision: I provided complete SDK code for @strapi/client because it's new and AI doesn't know it well. But I only provided requirements for React Query because that's standard and AI already knows it.
This balance lets the AI use its strengths while constraining its weaknesses with unfamiliar tools.
Project Structure
A visual file tree showing exactly where code should go. This prevents the AI from creating utils/api/strapi/client.ts when you wanted lib/strapi.ts.
Key Implementation Notes
Shows exact data access patterns:
1article.title
2article.featuredImage.url
3article.author.fullNameSpecifications & Acceptance Criteria
Five features with clear "done" criteria and checkboxes. Not vague like "build an articles page" but specific like "articles must display in 3/2/1 column grid, search must debounce 300ms, pagination must update URL."
Each criterion is testable and objective.
Implementation TODO Checklist
47 tasks broken down across 8 sequential phases: Foundation → API Layer → Hooks → Layout → Components → Pages → Routing → Polish.
Each task is specific and actionable (like "Create components/Header.tsx" rather than "build the header").
Verification Checkpoints
Checkpoints after phases 1-3, 4, 5, 6-7, and final verification. Encourages incremental testing—catch errors early before they compound.
Important Reminders
Seven key points repeated at the end (Strapi v5 patterns, exact field names, no populate needed, etc.).
Repetition matters because AI context windows can "forget" early instructions. Repeating critical constraints at the end keeps them active.
Here's what I learned through trial and error:
The Human-Readable Principle
I wrote the prompt as if I were handing it to another developer, not just an AI. This meant:
The Checklist Experiment
Adding the TODO checklist was an experiment. I wasn't sure if or how Lovable would use it.
But I included it for a few reasons:
Did it work? Hard to say definitively, but the code did appear in a logical order—foundation first, then data layer, then UI components, then pages. The incremental build felt more organized than previous attempts.
Finding the Balance
After trying prompts that were too vague ("build a blog"), too detailed (specifying every line), and everything in between, this version hit a workable middle ground:
Your Mileage May Vary
This structure worked well for my project, but it's not gospel. Depending on your backend complexity, team preferences, and what you're building, you might need more or less detail. The key insight: show the AI your actual data structure, and be specific about constraints that prevent common mistakes.
AI tools like Lovable.dev are powerful, but they have one big limitation: they don't know anything about YOUR specific project unless you tell them.
What the AI knows:
What the AI doesn't know:
Say "build a blog with Strapi" without details, and the AI will:
/api/posts when yours is /api/articlesarticle.attributes.title when yours is article.titleThis leads to additional debugging and prompting to fix what the AI guessed wrong. Remember, you're paying for each prompt you make.
Notice how we provided complete SDK code but not React Query code? Here's why:
Strapi-Specific (AI needs this):
1// This is Strapi-specific - AI needs the exact syntax
2const client = strapiClient({
3 baseURL: "https://your-strapi.strapiapp.com",
4});
5
6// How to use client.single() vs client.collection()
7const response = await client.single("global").find();
8const articles = await client.collection("articles").find({
9 filters: { slug: { $eq: slug } },
10});Standard React (AI knows this):
1// This is standard React Query - AI knows this pattern
2const { data, isLoading } = useQuery({
3 queryKey: ["articles"],
4 queryFn: getArticles,
5});Why we provided the Strapi code:
@strapi/client works$containsi aren't obviousOur prompt balances specific details with general instructions:
Be Specific About (Strapi stuff):
Be General About (React stuff):
This gives the AI:
Including real JSON responses from the API was crucial. Even though I shared the endpoints, Lovable didn't consistently call them to fetch the actual data:
1{
2 "data": [
3 {
4 "id": 12,
5 "title": "Article Title", // ← Strapi v5 structure!
6 "author": {
7 // ← Direct object (no .attributes)
8 "fullName": "Jane Doe"
9 },
10 "featuredImage": {
11 // ← Direct media object
12 "url": "https://..."
13 }
14 }
15 ]
16}This shows Lovable:
fullName not name, featuredImage not coverImagedata.attributes.title nestingalternativeText)contentTags are returnedWithout these examples, the AI might generate code for older Strapi versions (v3/v4 with nested .attributes), which wouldn't work with Strapi v5.
Let's compare:
Vague Prompt:
"Build a blog frontend for my Strapi backend"
Result:
Specific Prompt (What We Created):
Includes: Backend URL, SDK code, filter syntax, real responses, content types, block components, Strapi v5 data structure
Result:
While our checkpoint-based one-shot prompt seemed better than traditional one-shot prompting, I still don't think it's the best approach.
Issue #1: Overwhelming Scope Asking the AI to build an entire blog platform in one go led to:
Issue #2: Context Overload The comprehensive prompt, while detailed, tried to cover:
This caused the AI to sometimes lose track of specific requirements or make assumptions despite our safeguards.
Issue #3: Testing Bottlenecks When everything is generated at once:
Issue #4: Knowledge File Not Utilized In this one-shot approach we didn't leverage Lovable's Knowledge file feature, which means:
We tried one-shot prompting, now let's try the iterative approach.
Here's what Lovable recommends (and what we should have done from the start):
Why it matters: The Knowledge file is your project's brain. It gets sent with every prompt and helps the AI understand the full context without repeating yourself.
What to include:
1# Project Knowledge File
2
3## Product Vision
4
5{{One-sentence goal}} - Build a blog platform that consumes Strapi v5 CMS
6
7## Core Features
8
9- Dynamic landing page with 9 block types
10- Articles listing with search and pagination
11- Single article view with related articles
12- Responsive navigation and footer
13
14## Tech Stack
15
16- React 18 + TypeScript (strict mode)
17- Tailwind CSS + shadcn/ui
18- React Router v6
19- TanStack Query v5
20- Strapi Client SDK (@strapi/client)
21
22## Backend Context
23
24- Strapi v5 backend at: https://inviting-cheese-ce66d3b9e0.strapiapp.com
25- Direct field access (NO .attributes nesting)
26- Auto-populated relations via middleware
27- Filter syntax: { filters: { field: { $eq: value } } }
28
29## Data Model
30
31[Include your actual API field names from Phase 1 discovery]
32
33- Article: title, description, slug, content, featuredImage, author, contentTags, blocks, relatedArticles
34- Author: fullName (NOT name), bio, image
35- Landing Page Blocks: section-heading (heading, text NOT subheading), content-with-image (links NOT buttons, reversed NOT imagePosition), etc.
36
37## Design System
38
39- Mobile-first responsive
40- Tailwind config: [colors, fonts, spacing]
41- shadcn/ui components: Button, Card, Input, etc.
42
43## Coding Standards
44
45- TypeScript strict mode
46- ESLint + Prettier
47- Component pattern: function components with hooks
48- File structure: /components, /pages, /hooks, /lib, /types
49
50## Non-Goals (This Version)
51
52- User authentication
53- Comments system
54- Article editing (read-only frontend)You can auto-generate a Knowledge file by asking:
1Generate a knowledge file for my project based on the features already implemented.Instead of "build everything," break your project into milestones:
Milestone 0: Foundation
Milestone 1: Core Data Layer
Milestone 2: Layout & Navigation
Milestone 3: Landing Page
Milestone 4: Articles Listing
Milestone 5: Article Detail
Milestone 6: Search & Filters
Milestone 7: Polish
When to use Chat Mode:
Chat Mode Workflow:
1You: "I need to implement search functionality for articles.
2 What's the best approach using React Query and the Strapi Client SDK?"
3
4AI: [Suggests 3 approaches]
5
6You: "I like option 2. Can you create a detailed implementation plan
7 with file structure and key code snippets?"
8
9AI: [Creates plan]
10
11You: "This looks good. Implement the plan."Pro tip: Use Chat Mode to investigate without writing code:
1"Investigate but don't write code yet. Show me what needs to change."Bad Prompt (too broad):
1Build the articles page with search, pagination, and filters.Good Prompt (specific and scoped):
1On page /articles, implement article listing with these requirements:
2
3SCOPE:
4- Display articles in a responsive grid (3 cols desktop, 2 tablet, 1 mobile)
5- Use the useArticles() hook from /hooks/useStrapi.ts
6- Each article card shows: title, description, featuredImage, author.fullName, contentTags
7
8CONSTRAINTS:
9- Do not modify /lib/strapi.ts or /hooks/useStrapi.ts
10- Use ArticleCard component (to be created in /components/ArticleCard.tsx)
11- Follow Tailwind spacing from design system
12- Use loading skeleton, not spinner
13
14ACCEPTANCE TESTS:
15- Articles load and display correctly
16- Grid is responsive across breakpoints
17- Loading state shows skeleton cards
18- Error state shows user-friendly message
19
20DELIVERABLES:
21- /pages/ArticlesPage.tsx
22- /components/ArticleCard.tsx
23- Update /App.tsx routing if neededTell the AI what NOT to touch:
1CONSTRAINTS:
2- Do not modify /lib/strapi.ts (API layer is stable)
3- Do not change /types/strapi.ts (types are verified)
4- Do not edit /components/Layout.tsx (working correctly)
5- Keep existing ArticleCard API - only modify internalsIf your app has multiple user roles, always specify:
1As an Admin, I want to see an "Edit" button on each article card.
2As a Guest, I should only see the "Read More" link.
3
4Please isolate this feature and add role-based conditional rendering.Important note: The AI's memory can be limited. Don't assume it remembers your Knowledge file perfectly.
Repeat critical instructions in prompts:
1Reminder: We're using Strapi v5 with direct field access (article.title, NOT article.attributes.title).
2Reminder: author field is "fullName" not "name"
3Reminder: blocks.section-heading uses "text" not "subheading"After the AI generates code:
If something breaks:
For simple changes (text, colors, spacing), use Visual Edit instead of prompts:
After every working feature:
Based on Lovable's best practices, here's the recommended approach for building the Strapi blog:
Prompt 0.1: Generate Knowledge File
1Generate a knowledge file for a blog platform project that:
2- Consumes Strapi v5 backend at https://inviting-cheese-ce66d3b9e0.strapiapp.com
3- Uses React, TypeScript, Tailwind CSS, React Router, TanStack Query
4- Has landing page, articles listing, and article detail pages
5- Implements 9 dynamic zone block components
6
7Include: product vision, tech stack, data model, coding standards, and non-goals.Prompt 0.2: Setup Project & Dependencies
1CONTEXT: Starting a new Vite + React + TypeScript project
2
3TASK: Initialize project with these dependencies:
4- @strapi/client
5- @tanstack/react-query
6- react-router-dom
7- tailwindcss
8- Include shadcn/ui setup
9
10DELIVERABLES:
11- package.json with correct versions
12- vite.config.ts
13- tailwind.config.js
14- tsconfig.json with strict mode
15- Basic folder structure: /components, /pages, /hooks, /lib, /types
16
17Do not create any components yet.Prompt 1.1: Fetch & Document API Structure
1ROLE: You are a senior TypeScript developer
2
3TASK: API Discovery for Strapi v5 backend
4
5STEP 1: Fetch these endpoints and save responses:
6- https://inviting-cheese-ce66d3b9e0.strapiapp.com/api/global
7- https://inviting-cheese-ce66d3b9e0.strapiapp.com/api/landing-page
8- https://inviting-cheese-ce66d3b9e0.strapiapp.com/api/articles?pagination[page]=1&pagination[pageSize]=6
9
10STEP 2: Create a field name reference document listing:
11- All field names by content type
12- Nested object structures
13- Note any common naming pitfalls (e.g., "text" not "subheading", "fullName" not "name")
14
15STEP 3: Present findings and WAIT for confirmation
16
17DO NOT create TypeScript interfaces yet.Prompt 1.2: Create Verified TypeScript Types
1CONTEXT: Field names verified in previous step
2
3TASK: Create TypeScript interfaces in /types/strapi.ts
4
5REQUIREMENTS:
6- Use EXACT field names from API responses
7- Add JSDoc comments with field source
8- Include: Article, Author, Tag, Global, LandingPage, and all 9 block type interfaces
9- Create verification table for each interface
10
11DELIVERABLES:
12- /types/strapi.ts
13- Verification tables for all interfaces
14
15WAIT for confirmation before proceeding.Prompt 2.1: Strapi Client Configuration
1TASK: Setup Strapi Client SDK in /lib/strapi.ts
2
3SPEC:
4- Import strapiClient from '@strapi/client'
5- Configure baseURL: https://inviting-cheese-ce66d3b9e0.strapiapp.com
6- Create utility functions:
7 - getGlobal() - fetch global settings
8 - getLandingPage() - fetch landing page
9 - getArticles(params?) - fetch articles with optional search, page, pageSize
10 - getArticleBySlug(slug) - fetch single article
11
12CONSTRAINTS:
13- Do not add populate params (auto-populated by backend)
14- Use Strapi v5 filter syntax: { filters: { field: { $eq: value } } }
15- Return clean data (unwrap response.data)
16
17DELIVERABLES:
18- /lib/strapi.ts with 4 functions
19- Include inline comments explaining filter syntaxPrompt 2.2: React Query Hooks
1TASK: Create React Query hooks in /hooks/useStrapi.ts
2
3REQUIREMENTS:
4- Wrap each /lib/strapi.ts function with useQuery or useMutation
5- Use appropriate query keys
6- Handle loading, error, and success states
7- Enable proper caching
8
9FUNCTIONS TO CREATE:
10- useGlobal()
11- useLandingPage()
12- useArticles(search?, page?)
13- useArticle(slug)
14
15CONSTRAINTS:
16- Do not modify /lib/strapi.ts
17- Do not modify /types/strapi.ts
18
19DELIVERABLES:
20- /hooks/useStrapi.ts
21- Setup QueryClientProvider in /App.tsx if not already donePrompt 3.1: Global Layout Structure
1TASK: Create global layout in /components/Layout.tsx
2
3SCOPE:
4- Use useGlobal() hook to fetch header and footer data
5- Create Header component with navigation
6- Create Footer component
7- Add mobile hamburger menu
8
9CONSTRAINTS:
10- Do not implement banner yet (next prompt)
11- Use shadcn/ui components where appropriate
12- Mobile-first responsive design
13
14DELIVERABLES:
15- /components/Layout.tsx
16- /components/Header.tsx
17- /components/Footer.tsx
18
19ACCEPTANCE TESTS:
20- Header renders navigation links from API
21- Footer renders footer data from API
22- Mobile menu toggles correctly
23- Layout wraps all pagesPrompt 3.2: Add Dismissible Banner
1TASK: Add dismissible banner to /components/Layout.tsx
2
3REQUIREMENTS:
4- Render global.banner data
5- Add dismiss functionality (localStorage)
6- Show banner only if not dismissed
7- Style with Tailwind (top of page, full width)
8
9CONSTRAINTS:
10- Do not modify Header or Footer components
11- Use existing Layout structure
12
13ACCEPTANCE TEST:
14- Banner shows on first visit
15- Dismiss button hides banner
16- Banner stays hidden after refreshPrompt 4.1: Landing Page Foundation
1TASK: Create landing page structure at /pages/LandingPage.tsx
2
3REQUIREMENTS:
4- Use useLandingPage() hook
5- Render page title and description
6- Create BlockRenderer component skeleton
7- Map __component to block components
8
9CONSTRAINTS:
10- Do not implement actual block components yet
11- Create placeholder components for each block type
12- Focus on the rendering infrastructure
13
14DELIVERABLES:
15- /pages/LandingPage.tsx
16- /components/BlockRenderer.tsx (with placeholders)Prompt 4.2: Implement Hero Block
1TASK: Implement HeroBlock component at /components/blocks/HeroBlock.tsx
2
3SPEC (from API response):
4- heading: string
5- text: string
6- links: array of {label, url, variant}
7- image: {url, alternativeText}
8
9DESIGN:
10- Full-width hero section
11- Background image with overlay
12- Centered content
13- CTA buttons in flex row
14
15CONSTRAINTS:
16- Do not modify other block components
17- Use verified TypeScript interface from /types/strapi.ts
18- Reference API field names in comments
19
20ACCEPTANCE TEST:
21- Hero renders with actual data from landing page
22- Image loads with correct alt text
23- CTA buttons link to correct URLsPrompt 4.3: Implement Section Heading Block
1TASK: Implement SectionHeadingBlock at /components/blocks/SectionHeadingBlock.tsx
2
3CRITICAL: Use exact API field names:
4- block.heading (NOT block.title)
5- block.text (NOT block.subheading)
6
7DESIGN:
8- Centered text section
9- Large heading + smaller descriptive text
10- Responsive typography
11
12CONSTRAINTS:
13- Do not modify other components
14- Use Tailwind typography classes
15
16DELIVERABLES:
17- /components/blocks/SectionHeadingBlock.tsx
18
19VERIFICATION:
20Confirm you're using block.heading and block.text (not title/subheading)Continue this pattern for remaining blocks: CardGrid, ContentWithImage, Markdown, PersonCard, FAQs, Newsletter, FeaturedArticles
Prompt 5.1: Article Card Component
1TASK: Create reusable ArticleCard component
2
3SPEC:
4- Props: article (Article type)
5- Display: featuredImage, title, description, author.fullName, contentTags
6- Click navigates to /articles/:slug
7
8DESIGN:
9- Card with image on top
10- Hover effect
11- Tag pills
12- Author attribution
13
14CONSTRAINTS:
15- Do not create ArticlesPage yet
16- Use verified Article interface
17- Add "Read More" link
18
19DELIVERABLES:
20- /components/ArticleCard.tsxPrompt 5.2: Articles Listing Page
1TASK: Create /pages/ArticlesPage.tsx
2
3REQUIREMENTS:
4- Use useArticles() hook
5- Render ArticleCard grid (3 cols desktop, 2 tablet, 1 mobile)
6- Add loading skeleton
7- Add empty state
8
9CONSTRAINTS:
10- Do not add search or pagination yet (next prompts)
11- Use ArticleCard component (don't modify it)
12
13DELIVERABLES:
14- /pages/ArticlesPage.tsx
15- Update /App.tsx routingPrompt 5.3: Add Search Functionality
1TASK: Add search to /pages/ArticlesPage.tsx
2
3REQUIREMENTS:
4- Search input with 300ms debounce
5- Pass search query to useArticles hook
6- Show "No results" state
7- Clear search button
8
9CONSTRAINTS:
10- Do not modify ArticleCard component
11- Do not modify useArticles hook (it already supports search param)
12
13ACCEPTANCE TESTS:
14- Typing searches articles by title/description
15- Debouncing prevents excessive API calls
16- Results update correctlyPrompt 5.4: Add Pagination Controls
1TASK: Add pagination to /pages/ArticlesPage.tsx
2
3REQUIREMENTS:
4- Show current page, total pages
5- Previous/Next buttons
6- Jump to page input
7- Update URL query params: ?page=2
8
9CONSTRAINTS:
10- Do not modify useArticles hook
11- Pagination data comes from meta.pagination in API response
12
13ACCEPTANCE TESTS:
14- Pagination controls work correctly
15- URL updates with page number
16- Direct URL navigation works (/articles?page=3)Prompt 6.1: Article Detail Foundation
1TASK: Create /pages/ArticlePage.tsx
2
3REQUIREMENTS:
4- Get slug from URL params
5- Use useArticle(slug) hook
6- Display: featuredImage, title, description, author (with image), contentTags
7- Render markdown content
8- Show 404 if article not found
9
10CONSTRAINTS:
11- Do not render blocks yet (next prompt)
12- Do not render related articles yet (next prompt)
13
14DELIVERABLES:
15- /pages/ArticlePage.tsx
16- Update /App.tsx routing: /articles/:slugPrompt 6.2: Render Article Blocks
1TASK: Add block rendering to /pages/ArticlePage.tsx
2
3REQUIREMENTS:
4- Reuse BlockRenderer component from landing page
5- Render article.blocks array
6- Use same block components
7
8CONSTRAINTS:
9- Do not modify block components
10- Do not modify BlockRenderer
11
12ACCEPTANCE TEST:
13- Article blocks render correctly
14- All block types display properlyPrompt 6.3: Add Related Articles
1TASK: Add related articles section to /pages/ArticlePage.tsx
2
3REQUIREMENTS:
4- Render article.relatedArticles array
5- Use ArticleCard component in grid
6- "Related Articles" heading
7- Show only if relatedArticles exist
8
9CONSTRAINTS:
10- Do not modify ArticleCard
11
12ACCEPTANCE TEST:
13- Related articles display at bottom of page
14- Cards link to correct articlesPrompt 7.1: Error Boundaries
1TASK: Add error boundaries
2
3REQUIREMENTS:
4- Create ErrorBoundary component
5- Wrap each page with error boundary
6- User-friendly error messages
7- "Try again" button
8
9DELIVERABLES:
10- /components/ErrorBoundary.tsx
11- Update page wrappersPrompt 7.2: Accessibility Audit
1TASK: Accessibility improvements
2
3REQUIREMENTS:
4- Add proper ARIA labels
5- Ensure keyboard navigation works
6- Check color contrast
7- Add skip links
8- Alt text on all images
9
10ACCEPTANCE:
11- Lighthouse accessibility score > 95Prompt 7.3: Performance Optimization
1TASK: Performance optimization
2
3REQUIREMENTS:
4- Lazy load images
5- Code splitting for routes
6- Optimize bundle size
7- Add meta tags for SEO
8
9ACCEPTANCE:
10- Lighthouse performance score > 90| Aspect | One-Shot (Even with Checkpoints) | Iterative Multi-Prompt |
|---|---|---|
| Initial Time | Fast first output | Slower start |
| Total Time | Often slower (more debugging) | Faster to working product |
| Code Quality | Variable, hard to review | Consistently higher |
| Testing | Test everything at once | Test each piece |
| Debugging | Hard to isolate issues | Easy to pinpoint problems |
| AI Context | Context overload | Focused context |
| Knowledge Reuse | Limited | Knowledge file persists |
| Flexibility | Rigid structure | Adaptable as you learn |
| Learning Curve | Steep (all at once) | Gradual |
| Version Control | Large commits | Small, focused commits |
| Rollback | Lose all work | Rollback single feature |
Use Checkpoint One-Shot When:
Use Iterative Multi-Prompt When:
For our Strapi blog project, the iterative approach is strongly recommended.
When using AI coding tools:
The more accurate details you give about your Strapi backend, the better code you'll get.
txt block)[Paste actual response from /api/global...] with real JSONyarn strapi openapi generate in your serverspecification.json<openapi-spec> sectionAs Lovable builds your application:
Lovable.dev provides integrated deployment options:
Don't just provide the OpenAPI spec—include actual API responses from your endpoints. This ensures the AI understands your specific data structure (especially if you've customized it with middleware).
Fetch real responses:
# Get actual responses from your deployed Strapi
curl https://your-strapi.strapiapp.com/api/global
curl https://your-strapi.strapiapp.com/api/articlesSpecify you're using Strapi v5 in your prompt. Strapi v5 changed the data structure from v4—it no longer uses nested data.attributes, which makes your frontend code much cleaner.
Whenever you modify Strapi content types:
yarn strapi openapi generateThen update your Lovable prompt with the new spec.
If your Strapi has custom middleware (like our auto-populate middleware), document this clearly. In our case:
Don't wait until the frontend is "done" to test with real Strapi data:
Problem: Asking Lovable to "build a blog" without context results in generic code that doesn't match your API.
Solution: Provide OpenAPI spec, real API responses, and specific requirements about data structure and features.
Problem: AI generates code for older Strapi versions (v3/v4) with nested data.attributes structure when you're using Strapi v5's simpler structure.
Solution: Clearly specify you're using Strapi v5 in your prompt. Include actual API responses showing the v5 structure. Give examples of correct data access: article.title not article.attributes.title.
Problem: Not populating relations results in missing data, or populating everything causes performance issues.
Solution: Be specific about which relations to populate:
1For article listing: populate=coverImage,category,author
2For article detail: populate=*Problem: AI-generated code may create infinite loops or excessive API calls.
Solution: Implement debouncing, proper dependency arrays in useEffect, and rate limiting on your API:
1// Add to your prompt:
2"Implement search with 300ms debounce to prevent excessive API calls.
3Ensure useEffect hooks have proper dependency arrays to avoid infinite loops.
4Add request caching where appropriate."AI tools like Lovable.dev aren't replacing developers. They're making us faster and more creative. But you still need to understand your data, your API, and your app's structure to use them well.
Start with a clear Strapi backend, create your OpenAPI spec, write a detailed prompt, and let Lovable.dev speed up your work. Just remember: the AI is your coding partner, not your replacement.
Want to learn more? Join me Mon-Fri at 12:30 PM CST for Strapi's open office hours on Discord, or find me @codingthirty on X.
Resources: