Strapi Integration with Frontend (React/Vue/Next.js/Nuxt.js)

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Strapi Integration with Frontend (React/Vue/Next.js/Nuxt.js)
Medium
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Strapi Integration with Frontend (React/Vue/Next.js/Nuxt.js)

Strapi works as a separate headless API server. The frontend communicates with it via HTTP — through REST or GraphQL. Key integration tasks: type-safe client, framework-level caching, on-demand revalidation, and proxying requests through BFF to hide API tokens.

Basic API Client

// lib/strapi.ts
const STRAPI_URL = process.env.STRAPI_URL!
const API_TOKEN = process.env.STRAPI_API_TOKEN!

interface StrapiResponse<T> {
  data: T
  meta: {
    pagination?: { page: number; pageSize: number; pageCount: number; total: number }
  }
}

export async function strapi<T>(
  endpoint: string,
  init?: RequestInit & { next?: { tags?: string[]; revalidate?: number } }
): Promise<StrapiResponse<T>> {
  const url = `${STRAPI_URL}/api${endpoint}`

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    ...init,
  })

  if (!response.ok) {
    throw new Error(`Strapi error ${response.status}: ${await response.text()}`)
  }

  return response.json()
}

// Helper functions
export function getMediaURL(path: string | null | undefined) {
  if (!path) return null
  if (path.startsWith('http')) return path
  return `${STRAPI_URL}${path}`
}

Server Components (Next.js App Router)

// app/articles/page.tsx
import { strapi } from '@/lib/strapi'
import type { Article, StrapiMedia } from '@/types/strapi'

interface ArticleListData {
  id: number
  attributes: {
    title: string
    slug: string
    excerpt: string
    publishedAt: string
    cover: { data: { id: number; attributes: StrapiMedia } | null }
  }
}

export default async function ArticlesPage({
  searchParams,
}: {
  searchParams: { page?: string; category?: string }
}) {
  const page = Number(searchParams.page) || 1
  const categoryFilter = searchParams.category
    ? `&filters[category][slug][$eq]=${searchParams.category}`
    : ''

  const { data: articles, meta } = await strapi<ArticleListData[]>(
    `/articles?populate=cover,category&sort=publishedAt:desc&pagination[page]=${page}&pagination[pageSize]=12${categoryFilter}`,
    { next: { tags: ['articles'], revalidate: 3600 } }
  )

  return (
    <div>
      <ArticleGrid articles={articles} />
      <Pagination meta={meta.pagination} />
    </div>
  )
}

export const revalidate = 3600

ISR + On-demand Revalidation

// app/articles/[slug]/page.tsx
export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const { data } = await strapi<ArticleListData[]>(
    `/articles?filters[slug][$eq]=${params.slug}&populate=cover,author,category,tags`,
    { next: { tags: [`article-${params.slug}`] } }
  )

  const article = data[0]
  if (!article) notFound()

  return <Article article={article} />
}

export async function generateStaticParams() {
  const { data } = await strapi<{ attributes: { slug: string } }[]>(
    '/articles?fields[0]=slug&pagination[pageSize]=200'
  )
  return data.map(item => ({ slug: item.attributes.slug }))
}
// app/api/revalidate/route.ts — receives webhook from Strapi
import { revalidateTag, revalidatePath } from 'next/cache'

export async function POST(req: Request) {
  const { model, entry } = await req.json()

  // Invalidate by tag
  revalidateTag(model)  // e.g., 'article'

  // Invalidate specific page
  if (entry?.slug) {
    revalidatePath(`/articles/${entry.slug}`)
    revalidateTag(`article-${entry.slug}`)
  }

  return Response.json({ revalidated: true })
}

Client-side Requests (React)

// hooks/useArticles.ts
'use client'
import useSWR from 'swr'

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

export function useArticles(category?: string) {
  const query = category ? `?filters[category][slug][$eq]=${category}` : ''
  const { data, error, isLoading } = useSWR(
    `/api/articles${query}`,  // via Next.js API proxy
    fetcher
  )

  return {
    articles: data?.data || [],
    isLoading,
    error,
  }
}
// app/api/articles/route.ts — proxy for client requests
export async function GET(req: Request) {
  const url = new URL(req.url)
  const params = url.searchParams.toString()

  const response = await fetch(
    `${process.env.STRAPI_URL}/api/articles?${params}&populate=cover`,
    { headers: { Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}` } }
  )

  const data = await response.json()
  return Response.json(data)
}

TypeScript Types for Strapi Responses

// types/strapi.ts
export interface StrapiMedia {
  name: string
  url: string
  alternativeText: string | null
  width: number
  height: number
  formats: {
    thumbnail?: StrapiMediaFormat
    small?: StrapiMediaFormat
    medium?: StrapiMediaFormat
    large?: StrapiMediaFormat
  }
}

export interface StrapiMediaFormat {
  url: string
  width: number
  height: number
}

export interface StrapiEntity<T> {
  id: number
  attributes: T
}

export interface Article {
  title: string
  slug: string
  content: string
  excerpt: string
  publishedAt: string
  cover: { data: StrapiEntity<StrapiMedia> | null }
  author: { data: StrapiEntity<{ name: string; avatar: any }> | null }
}

Timeline

Integrating Strapi with Next.js (ISR, webhooks, TypeScript types) — 2–3 days.