Skip to main content
Prerequisites:
  • Next.js 14.0 or higher (for Server Actions)
  • TypeScript enabled in your Next.js project
  • Strapi v4 or v5 backend
Strapi2Front generates TypeScript types, services, and Zod schemas for your Next.js applications. Server Actions generation for Next.js is coming soon.

Current Support

TypeScript Types

Fully typed interfaces for all content types

Service Functions

Ready-to-use data fetching functions

Zod Schemas

Validation schemas for forms and mutations

Server Actions

Coming soon in a future release

Setup

1

Install Strapi2Front

Install the package in your Next.js project:
npm install strapi2front
2

Initialize Configuration

Run the init command to create a configuration file:
npx strapi2front init
3

Configure for Next.js

Update your strapi2front.config.ts:
strapi2front.config.ts
import { defineConfig } from 'strapi2front';

export default defineConfig({
  strapiUrl: process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337',
  strapiVersion: 'v5', // or 'v4'
  output: {
    path: './src/lib/strapi',
    structure: 'by-feature',
  },
  features: {
    types: true,
    services: true,
    schemas: true,
    // Server Actions coming soon
  },
});
4

Add Environment Variables

Create a .env.local file:
.env.local
NEXT_PUBLIC_STRAPI_URL=https://your-strapi-instance.com
STRAPI_API_TOKEN=your-api-token-here
5

Generate Code

Run the sync command:
npx strapi2front sync
This generates:
src/lib/strapi/
├── collections/
│   └── article/
│       ├── types.ts
│       ├── schemas.ts
│       └── service.ts
├── singles/
│   └── homepage/
│       ├── types.ts
│       ├── schemas.ts
│       └── service.ts
└── shared/
    ├── client.ts
    └── utils.ts

Usage

Server Components

Fetch data in React Server Components using the generated services:
import { articleService } from '@/lib/strapi/collections/article/service';

export default async function BlogPage() {
  // Fetch articles with pagination
  const { data: articles, pagination } = await articleService.findMany({
    pagination: {
      page: 1,
      pageSize: 10,
    },
    sort: ['publishedAt:desc'],
    populate: ['author', 'coverImage'],
  });

  return (
    <div className="blog-grid">
      {articles.map((article) => (
        <article key={article.documentId}>
          <h2>{article.title}</h2>
          <p>{article.excerpt}</p>
          <a href={`/blog/${article.slug}`}>Read more</a>
        </article>
      ))}
      
      <Pagination 
        currentPage={pagination.page}
        totalPages={pagination.pageCount}
      />
    </div>
  );
}

Client Components with SWR

Use SWR for client-side data fetching:
'use client';

import useSWR from 'swr';
import type { Article } from '@/lib/strapi/collections/article/types';
import type { StrapiPagination } from '@/lib/strapi/shared/utils';

interface ArticleResponse {
  data: Article[];
  pagination: StrapiPagination;
}

const fetcher = (url: string) => fetch(url).then(r => r.json());

export function ArticleList() {
  const { data, error, isLoading } = useSWR<ArticleResponse>(
    '/api/articles',
    fetcher
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load articles</div>;
  if (!data) return null;

  return (
    <div className="article-list">
      {data.data.map((article) => (
        <div key={article.documentId}>
          <h3>{article.title}</h3>
          <p>{article.excerpt}</p>
        </div>
      ))}
    </div>
  );
}

Route Handlers

Create API routes using the generated services:
app/api/contact/route.ts
import { contactService } from '@/lib/strapi/collections/contact/service';
import { contactSchema } from '@/lib/strapi/collections/contact/schemas';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  try {
    const body = await request.json();
    
    // Validate with Zod schema
    const validatedData = contactSchema.parse(body);
    
    // Create contact entry
    const contact = await contactService.create(validatedData);
    
    return NextResponse.json({ success: true, data: contact });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', details: error.errors },
        { status: 400 }
      );
    }
    
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Form Handling with React Hook Form

Use Zod schemas with React Hook Form:
components/ContactForm.tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { contactSchema } from '@/lib/strapi/collections/contact/schemas';
import type { z } from 'zod';

type ContactFormData = z.infer<typeof contactSchema>;

export function ContactForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<ContactFormData>({
    resolver: zodResolver(contactSchema),
  });

  const onSubmit = async (data: ContactFormData) => {
    const response = await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });

    if (response.ok) {
      alert('Message sent successfully!');
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} placeholder="Name" />
      {errors.name && <span>{errors.name.message}</span>}

      <input {...register('email')} type="email" placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}

      <textarea {...register('message')} placeholder="Message" />
      {errors.message && <span>{errors.message.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  );
}

Service Functions Reference

All generated services include these methods:

Collection Types

Fetch multiple items with pagination:
const { data, pagination } = await articleService.findMany({
  filters: {
    publishedAt: { $notNull: true },
  },
  pagination: {
    page: 1,
    pageSize: 25,
  },
  sort: ['publishedAt:desc'],
  populate: ['author'],
});

Single Types

  • find(options) — Fetch single type data
  • update(data) — Update single type
  • delete() — Delete single type

Advanced Patterns

Data Fetching Strategies

Generate static pages at build time:
export default async function ArticlePage({ params }) {
  const article = await articleService.findBySlug(params.slug);
  return <Article data={article} />;
}

export async function generateStaticParams() {
  const articles = await articleService.findAll();
  return articles.map(a => ({ slug: a.slug }));
}

Caching with Next.js

Leverage Next.js caching for optimal performance:
import { cache } from 'react';
import { articleService } from '@/lib/strapi/collections/article/service';

// Cache article fetching per request
export const getArticle = cache(async (slug: string) => {
  return await articleService.findBySlug(slug, {
    populate: ['author', 'categories'],
  });
});

// Use in multiple components without refetching
export default async function ArticlePage({ params }) {
  const article = await getArticle(params.slug);
  return <Article data={article} />;
}

Parallel Data Fetching

Fetch multiple resources in parallel:
export default async function HomePage() {
  // Fetch in parallel
  const [homepage, articles, categories] = await Promise.all([
    homepageService.find(),
    articleService.findMany({ pagination: { pageSize: 5 } }),
    categoryService.findAll(),
  ]);

  return (
    <main>
      <Hero data={homepage.data} />
      <FeaturedArticles articles={articles.data} />
      <Categories categories={categories} />
    </main>
  );
}

Server Actions (Coming Soon)

Server Actions generation for Next.js is in development and will be available in a future release.
When available, you’ll be able to use Server Actions like this:
// Future syntax (not yet available)
import { createArticle, updateArticle } from '@/lib/strapi/actions/article';

export default function ArticleForm() {
  return (
    <form action={createArticle}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create</button>
    </form>
  );
}

Best Practices

Use Server Components by default and only mark components as 'use client' when you need interactivity.
Never expose your STRAPI_API_TOKEN to the client. Always use it in Server Components or API routes.
Use the findAll() method sparingly in production, as it fetches all items. Prefer findMany() with pagination for better performance.

Troubleshooting

Fetch Errors

If you encounter fetch errors, check that your NEXT_PUBLIC_STRAPI_URL is correct and accessible.

Type Errors

Run npx strapi2front sync to regenerate types after schema changes.

Environment Variables

Remember that only variables prefixed with NEXT_PUBLIC_ are available in client components.

Next Steps