These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Same.new?
Same.new is an AI-powered Next.js development platform built exclusively for Next.js applications. Backed by Y Combinator (Winter 2024) and powered by Claude 4.5 Opus, it translates natural language descriptions into complete React-based codebases, including components, pages, API routes, and project structure.
You describe what you need through a chat interface. Same.new generates the Next.js code using Claude 4.5 Opus. You can then make follow-up chat-based edits for further refinement or directly edit the generated code for customization. You can then export the entire project for independent deployment, where you would manually add Strapi API integration code if needed.
A few things worth knowing upfront:
- Framework scope: Next.js only. No Vue, Angular, or Svelte support.
- Languages: TypeScript and JavaScript.
- Native integrations: Neon (PostgreSQL), Supabase, and Clerk, all configured through Model Context Protocol (MCP) tools via natural language.
- No programmatic API: Same.new doesn't expose REST endpoints, SDKs, or CLI tools. Everything runs through the web-based UI. There's no npm package to install, no API to call from a CI pipeline, and no CLI available. The platform generates your starting point through interactive development, and you take it from there.
Sign up for the Logbook, Strapi's Monthly newsletter
Why Integrate Same.new with Strapi
Same.new generates Next.js frontend scaffolding through natural language prompts, while Strapi 5 provides a headless content management system (CMS) with auto-generated REST and GraphQL APIs.
Developers use Same.new to quickly bootstrap a Next.js application structure, then manually integrate Strapi's API endpoints into the generated project using standard fetch or axios patterns. This separation of concerns enables flexible content management through Strapi without being locked into a specific frontend technology. Here's why the combination works:
- Skip boilerplate on both sides. Same.new generates complete Next.js project structures with component architecture and pages. Strapi's Content-Type Builder auto-generates REST and GraphQL endpoints for every content model. You spend less time on scaffolding and more on the logic that differentiates your app. Integrating the two requires adding Strapi API consumption code to the generated Next.js project after export.
- Content stays decoupled from presentation. Strapi's headless architecture means content editors work in the admin panel while developers control the frontend independently. Regenerating or swapping the UI doesn't touch the content layer, and the API-first design works with any frontend framework.
- Non-developers can manage content. Once the integration is live, content updates happen through Strapi's Admin Panel rather than code deployments. This keeps your team out of the deployment cycle for copy changes, new blog posts, or product updates.
- Both tools favor the same stack. Same.new outputs Next.js applications using JavaScript or TypeScript. Strapi 5 provides an official
@strapi/clientlibrary (JavaScript/TypeScript SDK) designed for consuming REST and GraphQL APIs in Next.js applications. The patterns align for frontend integration without adaptation layers. - You keep full control of the code. Same.new generates complete, exportable Next.js projects you can edit directly. Strapi is open source with a plugin architecture for extending functionality. Neither tool locks you into a pattern you can't customize.
- Scale content delivery without scaling complexity. Strapi supports internationalization, role-based access, and media management out of the box. As your project grows, the content infrastructure grows with it.
How to Integrate Same.new with Strapi
The integration follows a practical pattern where you set up Strapi as your content backend, generate the Next.js frontend with Same.new using natural language prompts, then manually add API integration code to wire the two together using standard Next.js patterns for consuming Strapi's REST or GraphQL APIs.
Prerequisites
Before starting, make sure you have:
- Node.js v20, v22, or v24 (active LTS or Maintenance LTS)
- npm v6+ (or yarn/pnpm)
- A Same.new account at same.new
- Basic familiarity with Next.js and React
- A code editor for modifying the exported project
Step 1: Create and Configure Your Strapi Project
Start by setting up a fresh Strapi 5 instance:
npx create-strapi@latest my-strapi-backendThe interactive installer prompts for configuration options. For local development, the defaults (SQLite database) work fine. Once installation completes, start the development server:
cd my-strapi-backend && npm run developThe Admin Panel launches at http://localhost:1337/admin. Create your administrator account on first visit.
Step 2: Define Your Content Types
Content types define the structure of your data and the auto-generated REST and GraphQL API responses your frontend will consume. Navigate to the Content-Type Builder in the Admin Panel and create the structures your application needs.
For this guide, create an Article Collection Type with these fields:
| Field Name | Type | Options |
|---|---|---|
| title | Text | Required |
| slug | UID | Attached to "title" |
| content | Rich Text | |
| excerpt | Text | |
| coverImage | Media | Single image |
| publishedAt | DateTime |
After saving, Strapi automatically generates REST endpoints at /api/articles for standard CRUD operations without requiring manual route handlers or controllers. GraphQL support requires explicit configuration in config/plugins.js to expose a /graphql endpoint.
While Strapi eliminates boilerplate for basic read and write operations, developers may still need to create custom controllers and services for business logic beyond standard CRUD, as well as configure API token permissions and role-based access control for security.
Add a few sample entries through the Content Manager so you have data to work with in later steps.
Step 3: Generate an API Token
Your frontend needs to authenticate against Strapi's Content API since all content types are private by default. You can do this using API token authentication, JWT user authentication via the Users & Permissions plugin, or by explicitly configuring role-based permissions for the Authenticated role. API token authentication is the recommended approach for frontend applications.
- Go to Settings → Global settings → API Tokens
- Click Create new API Token
- Set the name (e.g., "Frontend Read Access")
- Choose Read-only for the token type
- Set token duration based on your use case
- Save and copy the generated token immediately (it won't be shown again)
Store this token securely. It goes into environment variables in your Next.js project, never into source code.
Step 4: Configure CORS for Frontend Access
By default, Strapi's CORS middleware uses wildcard origins, which can cause issues with credential-based requests. Configure explicit origins in config/middlewares.ts (or .js):
export default [
'strapi::logger',
'strapi::errors',
{
name: 'strapi::cors',
config: {
origin: ['http://localhost:3000', 'https://your-production-domain.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
credentials: true,
},
},
'strapi::poweredBy',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];Replace the origin URLs with the actual domains where your frontend will run. For local development, http://localhost:3000 is the Next.js default.
Step 5: Scaffold Your Frontend with Same.new
Open same.new and describe the application you need. Be specific about the structure and components, since Same.new generates the entire Next.js project from your prompt.
An effective prompt for a blog frontend might look like this:
Build a blog application using Next.js App Router and TypeScript. Include a homepage that displays article cards in a grid layout with title, excerpt, cover image, and publication date. Include an individual article page with full content rendering. Use Tailwind CSS for styling. Add a responsive navigation bar and footer. The data will come from a Strapi v5 REST or GraphQL API, so integrate the official @strapi/client library with API token authentication to fetch article content types and related data.
Same.new generates complete Next.js projects through natural language prompts, including React component files, page routes with API routes, layouts, and styling configured with TypeScript or JavaScript.
After AI generates the initial project scaffold, developers can iteratively refine it through natural language chat instructions (e.g., "Add a contact form," "Change the color scheme") or by directly editing the generated code in the editor for fine-tuning and customization. Once satisfied, developers can export the complete, production-ready Next.js project for independent deployment.
Export options: You can push to GitHub or download the project files. Either way, you need local access to the codebase for the next steps.
Step 6: Add the Strapi Client Library
Clone or extract your exported project, then install the official Strapi client:
cd my-same-new-blog
npm install @strapi/clientCreate an environment file for your API configuration:
# .env.local
STRAPI_API_URL=http://localhost:1337
STRAPI_API_TOKEN=your-api-token-from-step-3
NEXT_PUBLIC_STRAPI_API_URL=http://localhost:1337The STRAPI_API_TOKEN variable (without the NEXT_PUBLIC_ prefix) stays server-side only, keeping your token out of client bundles. The NEXT_PUBLIC_STRAPI_API_URL variant is safe for client-side use since it only contains the base URL.
Step 7: Create a Strapi Service Module
Set up a centralized module for Strapi API interactions. Create lib/strapi.ts in your project:
import { strapi } from '@strapi/client';
const client = strapi({
baseURL: `${process.env.STRAPI_API_URL}/api`,
auth: process.env.STRAPI_API_TOKEN,
});
export async function getArticles(page: number = 1, pageSize: number = 10) {
const articles = await client.collection('articles').find({
populate: ['coverImage'],
sort: ['publishedAt:desc'],
pagination: {
page,
pageSize,
},
});
return articles;
}
export async function getArticleBySlug(slug: string) {
const articles = await client.collection('articles').find({
filters: {
slug: {
$eq: slug,
},
},
populate: ['coverImage'],
});
return articles.data?.[0] || null;
}This module encapsulates all Strapi communication. The @strapi/client library provides a typed interface for API token authentication and content querying, requiring explicit configuration of the API base URL and authentication token.
Step 8: Connect Strapi Data to Your Pages
Replace the placeholder data fetching in your Same.new-generated pages with the Strapi service functions. Here's what the articles listing page looks like:
// app/page.tsx
import { getArticles } from '@/lib/strapi';
import ArticleCard from '@/components/ArticleCard';
export default async function HomePage() {
const { data: articles, meta } = await getArticles();
return (
<main className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Latest Articles</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{articles.map((article) => (
<ArticleCard
key={article.documentId}
title={article.title}
excerpt={article.excerpt}
slug={article.slug}
coverImage={article.coverImage}
publishedAt={article.publishedAt}
/>
))}
</div>
</main>
);
}This component uses server-side data fetching compatible with Next.js 13+ App Router. Note that coverImage must be explicitly populated (as configured in the service module), since Strapi v5 doesn't populate relations or media fields by default.
The component uses documentId as the key, which is Strapi v5's primary identifier in its flattened response structure.
And the individual article page:
// app/articles/[slug]/page.tsx
import { getArticleBySlug, getArticles } from '@/lib/strapi';
import { notFound } from 'next/navigation';
interface ArticlePageProps {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
const { data: articles } = await getArticles(1, 100);
return articles.map((article) => ({
slug: article.slug,
}));
}
export default async function ArticlePage({ params }: ArticlePageProps) {
const { slug } = await params;
const article = await getArticleBySlug(slug);
if (!article) {
notFound();
}
return (
<article className="container mx-auto px-4 py-8 max-w-3xl">
{article.coverImage && (
<img
src={`${process.env.NEXT_PUBLIC_STRAPI_API_URL}${article.coverImage.url}`}
alt={article.coverImage.alternativeText || article.title}
className="w-full rounded-lg mb-8"
/>
)}
<h1 className="text-4xl font-bold mb-4">{article.title}</h1>
<time className="text-gray-500 mb-8 block">
{new Date(article.publishedAt).toLocaleDateString()}
</time>
<div
className="prose prose-lg"
dangerouslySetInnerHTML={{ __html: article.content }}
/>
</article>
);
}Note a few things about Strapi 5's response structure: attributes are flattened directly onto the data object (no nested attributes key), and each document has both a numeric id and a string documentId. Use documentId as your primary identifier for API operations.
Step 9: Verify the Integration
Start both services and confirm data flows correctly:
Terminal 1 (Strapi):
cd my-strapi-backend && npm run developTerminal 2 (Next.js):
cd my-same-new-blog && npm run devOpen http://localhost:3000 in your browser. You should see your Strapi content rendered through the Same.new-generated UI. If articles aren't appearing, check these common issues:
- 401 Unauthorized: Verify your API token is correctly set in
.env.localand that the token has the appropriate permissions for the content type you're querying. When querying related content types, ensure the authenticated role has read permissions on both the primary content type and all related content types. - Empty response: Confirm you've published your content entries in Strapi's Content Manager (draft entries aren't returned by default). Additionally, verify that API tokens have read permissions configured for the content type in Settings → Roles.
- CORS errors: Double-check that
http://localhost:3000is listed in your CORS origin configuration in config/middlewares.ts. Ensure you're using explicit origin values in arrays rather than wildcard configurations when making credential-based requests. - Missing relations or media: Strapi v5 doesn't populate relations or media fields by default. Confirm your queries include the
populateparameter with specific relation names or usepopulate=*for all relations.
Project Example: AI-Scaffolded Developer Blog with Strapi CMS
Let's build something more complete: a developer blog where Same.new generates the frontend and Strapi manages articles, authors, and categories. This example demonstrates relational content, filtered queries, and dynamic routing.
Content Architecture in Strapi
Create Collection Types in the Content-Type Builder according to your content requirements. For example, in a recipe application, you might create Collection Types for "Recipe", "Author", and "Category" content types. Use the GUI-based interface or code-based schema definition methods documented in the Content-Type Builder to design your content structure.
Author:
| Field | Type | Options |
|---|---|---|
| name | Text | Required |
| bio | Text | |
| avatar | Media | Single |
Category:
| Field | Type | Options |
|---|---|---|
| name | Text | Required |
| slug | UID | Required |
Post:
| Field | Type | Options |
|---|---|---|
| title | Text | Required |
| slug | UID | Attached to "title" |
| content | Rich Text | |
| excerpt | Text | |
| coverImage | Media | Single image |
| author | Relation | Many-to-One with Author |
| categories | Relation | Many-to-Many with Category |
After saving, configure permissions under Settings → Users & Permissions plugin → Roles → Public. Enable find and findOne for Post, Author, and Category. This is critical: when your query populates the author relation, Strapi requires that the authenticated user's role has read permissions on both the primary content type (Post) and all related content types referenced in the query (Author and Category). If the Public role doesn't have read access to these related content types, relation fields will return as empty.
Generate the Frontend
Use this prompt in Same.new:
Create a developer blog with Next.js App Router and TypeScript using Tailwind CSS. The blog should have: a homepage showing featured and recent posts in a card grid, a category page that filters posts by category, individual post pages with author bio sidebar, a top navigation bar with links to Home and categories. Use a dark theme. Make data fetching functions modular so I can connect an external CMS. Use server components for data fetching.
Export the generated project and install dependencies:
cd developer-blog
npm install @strapi/client qs
npm install --save-dev @types/qsStrapi Service Layer
Create a comprehensive service module at lib/strapi.ts:
import { strapi } from '@strapi/client';
const client = strapi({
baseURL: `${process.env.STRAPI_API_URL}/api`,
auth: process.env.STRAPI_API_TOKEN,
});
export async function getPosts(page = 1, pageSize = 9) {
return client.collection('posts').find({
populate: ['author', 'coverImage', 'categories'],
sort: ['publishedAt:desc'],
pagination: { page, pageSize },
});
}
export async function getPostBySlug(slug: string) {
const result = await client.collection('posts').find({
filters: { slug: { $eq: slug } },
populate: ['coverImage', 'author', 'author.avatar', 'categories'],
});
return result.data?.[0] || null;
}
export async function getPostsByCategory(categorySlug: string, page = 1) {
const result = await client.collection('posts').find({
filters: {
categories: {
slug: { $eq: categorySlug },
},
},
populate: ['coverImage', 'author', 'categories'],
sort: ['publishedAt:desc'],
pagination: { page, pageSize: 9 },
});
return result;
}
export async function getCategories() {
return client.collection('categories').find({
sort: ['name:asc'],
});
}
export async function getFeaturedPosts() {
return client.collection('posts').find({
populate: ['coverImage', 'author', 'categories'],
sort: ['publishedAt:desc'],
pagination: { page: 1, pageSize: 3 },
});
}For queries with deeply nested populations, the qs library helps you construct properly formatted URLs without manual string manipulation, as recommended in the Strapi REST API parameters guide.
import qs from 'qs';
export async function getPostWithDeepRelations(slug: string) {
const query = qs.stringify(
{
filters: { slug: { $eq: slug } },
populate: {
coverImage: { fields: ['url', 'alternativeText', 'width', 'height'] },
author: {
populate: ['avatar'],
},
categories: {
fields: ['name', 'slug'],
},
},
},
{ encodeValuesOnly: true }
);
const response = await fetch(
`${process.env.STRAPI_API_URL}/api/posts?${query}`,
{
headers: {
Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}`,
},
next: { revalidate: 60 },
}
);
if (!response.ok) {
throw new Error(`Failed to fetch post: ${response.status}`);
}
const { data } = await response.json();
return data?.[0] || null;
}The next: { revalidate: 60 } option enables Incremental Static Regeneration (ISR), revalidating cached data every 60 seconds before re-fetching. This provides static-like performance with content that stays reasonably fresh.
Category Filtered Page
Connect the category filter page to Strapi:
// app/category/[slug]/page.tsx
import { getPostsByCategory, getCategories } from '@/lib/strapi';
import PostGrid from '@/components/PostGrid';
import { notFound } from 'next/navigation';
interface CategoryPageProps {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
const { data: categories } = await getCategories();
return categories.map((cat) => ({ slug: cat.slug }));
}
export default async function CategoryPage({ params }: CategoryPageProps) {
const { slug } = await params;
const { data: posts, meta } = await getPostsByCategory(slug);
if (!posts.length) {
notFound();
}
const categoryName = posts[0]?.categories?.find(
(c: { slug: string }) => c.slug === slug
)?.name;
return (
<main className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-2">{categoryName || slug}</h1>
<p className="text-gray-400 mb-8">
{meta.pagination.total} {meta.pagination.total === 1 ? 'post' : 'posts'}
</p>
<PostGrid posts={posts} />
</main>
);
}Post Detail Page with Author Sidebar
// app/posts/[slug]/page.tsx
import { getPostBySlug, getPosts } from '@/lib/strapi';
import { notFound } from 'next/navigation';
interface PostPageProps {
params: Promise<{ slug: string }>;
}
export async function generateStaticParams() {
const { data: posts } = await getPosts(1, 50);
return posts.map((post) => ({ slug: post.slug }));
}
export default async function PostPage({ params }: PostPageProps) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) {
notFound();
}
const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_API_URL;
return (
<div className="container mx-auto px-4 py-8 flex flex-col lg:flex-row gap-8">
<article className="lg:w-2/3">
{post.coverImage && (
<img
src={`${strapiUrl}${post.coverImage.url}`}
alt={post.coverImage.alternativeText || post.title}
className="w-full rounded-lg mb-6"
/>
)}
<div className="flex gap-2 mb-4">
{post.categories?.map((cat: { documentId: string; name: string; slug: string }) => (
<a
key={cat.documentId}
href={`/category/${cat.slug}`}
className="text-sm bg-blue-900 text-blue-200 px-3 py-1 rounded-full"
>
{cat.name}
</a>
))}
</div>
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<time className="text-gray-500 block mb-8">
{new Date(post.publishedAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
<div
className="prose prose-invert prose-lg max-w-none"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
</article>
{post.author && (
<aside className="lg:w-1/3">
<div className="sticky top-8 bg-gray-800 rounded-lg p-6">
{post.author.avatar && (
<img
src={`${strapiUrl}${post.author.avatar.url}`}
alt={post.author.name}
className="w-20 h-20 rounded-full mb-4"
/>
)}
<h3 className="text-lg font-semibold">{post.author.name}</h3>
{post.author.bio && (
<p className="text-gray-400 mt-2 text-sm">{post.author.bio}</p>
)}
</div>
</aside>
)}
</div>
);
}Deployment
For production, deploy each service independently:
Strapi backend: Deploy to Strapi Cloud, a VPS, or any Node.js-compatible platform. Set your production environment variables:
NODE_ENV=production
DATABASE_URL=postgresql://user:password@host:5432/dbname
ADMIN_JWT_SECRET=your-admin-secret
JWT_SECRET=your-jwt-secret
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=your-token-saltNext.js frontend: Deploy to Vercel, Netlify, or any platform supporting Next.js. Update environment variables to point at your production Strapi URL:
STRAPI_API_URL=https://your-strapi-backend.com
STRAPI_API_TOKEN=your-production-api-token
NEXT_PUBLIC_STRAPI_API_URL=https://your-strapi-backend.comRemember to update your Strapi CORS configuration with the production frontend domain before deploying. According to the Strapi v5 CORS configuration documentation, you must explicitly configure allowed origins in your config/middlewares.ts file with a structure like:
origin: ['http://localhost:3000', 'https://yourdomain.com']This replaces the default wildcard origin, which can cause conflicts when making credential-based requests from your frontend.
Next Steps
You now have a working integration where Same.new provides the frontend scaffold and Strapi manages structured content through auto-generated APIs. From here, consider adding webhook-based revalidation so your Next.js pages update automatically when content changes in Strapi.
You can also explore Strapi's Draft & Publish workflow for content preview before going live, or add search functionality using Strapi's filtering and full-text search capabilities.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.
For more details, visit the Strapi documentation for comprehensive guidance on Strapi v5 features including REST/GraphQL APIs, content modeling, authentication, and deployment, and the Same.new documentation for details on AI-powered Next.js development, integrations with Neon, Supabase, and Clerk, and natural language prompting capabilities.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.