Conectando Strapi con Next.js: crea tu frontend con datos dinámicos
🧱 Ya tengo montado mi backend con Strapi, ¡pero no sirve de mucho si no lo muestro!
Ahora toca conectar ese CMS headless con un frontend hecho en Next.js. Vamos paso a paso.
🧠 ¿Por qué Next.js?
Next.js es perfecto para esto porque:
- Soporta renderizado híbrido (SSR y SSG)
- Tiene routing automático
- Es fácil de desplegar en Vercel 🚀
- Puedes crear un sitio dinámico que se actualice con los datos de Strapi
📦 Preparar Next.js
- Crea tu proyecto:
1
2
3
npx create-next-app@latest frontend-strapi
cd frontend-strapi
npm run dev
- Instala
axios
para consumir la API:
1
npm install axios
🗂️ ¿Cómo organizar el frontend y el backend?
En este tipo de proyectos tendremos dos frameworks, uno encargado del backend (Strapi) y del front-end (Next.js), cada uno va en su propia carpeta, así:
1
2
3
mi-proyecto/
├── backend-strapi/ ← Strapi
└── frontend-nextjs/ ← Next.js
🛠️ ¿Cómo los ejecuto a la vez?
Hay dos opciones:
🧩 Opción 1: Ejecutarlos manualmente en dos terminales
- Abre una terminal y arranca Strapi:
1
2
cd backend-strapi
npm run develop
- Abre otra terminal y arranca Next.js:
1
2
cd frontend-nextjs
npm run dev
Esto es lo más habitual cuando estás desarrollando localmente.
🧰 Opción 2: Unificar con concurrently
(opcional)
Si te molesta tener dos terminales abiertas, puedes crear una carpeta raíz y un script para lanzar ambos.
- Estructura:
1
2
3
4
mi-proyecto/
├── backend-strapi/
├── frontend-nextjs/
└── package.json ← aquí va el script unificado
- Instala
concurrently
en la raíz:
1
2
npm init -y
npm install concurrently --save-dev
- En el
package.json
raíz, añade:
1
2
3
"scripts": {
"dev": "concurrently \"npm run develop --prefix backend-strapi\" \"npm run dev --prefix frontend-nextjs\""
}
- Ahora puedes lanzar ambos con:
1
npm run dev
🌐 Configuración de URLs
Asegúrate de que Next.js apunte correctamente al backend. En desarrollo local:
- Strapi suele correr en
http://localhost:1337
- Next.js corre en
http://localhost:3000
En tu frontend-nextjs/.env.local
:
NEXT_PUBLIC_API_URL=http://localhost:1337/api
Y en tu código usa esa variable:
1
const API_URL = process.env.NEXT_PUBLIC_API_URL;
🔗 Conectar con la API de Strapi
Supongamos que en Strapi tienes una colección articulos
con campos titulo
, slug
y contenido
.
Creamos un archivo en lib/api.js
para centralizar las llamadas:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// lib/api.js
import axios from 'axios';
const API_URL = 'http://localhost:1337/api'; // o el dominio en producción
export const getArticulos = async () => {
const res = await axios.get(`${API_URL}/articulos?populate=*`);
return res.data.data;
};
export const getArticuloPorSlug = async (slug) => {
const res = await axios.get(`${API_URL}/articulos?filters[slug][$eq]=${slug}&populate=*`);
return res.data.data[0];
};
🧭 Crear rutas dinámicas
1. Lista de artículos (pages/index.js
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { getArticulos } from "@/lib/api";
export default function Home({ articulos }) {
return (
<main className="p-8">
<h1 className="text-3xl font-bold mb-4">Artículos</h1>
<ul>
{articulos.map(({ id, attributes }) => (
<li key={id}>
<a href={'/articulo/${attributes.slug}'} className="text-blue-600 underline">
{attributes.titulo}
</a>
</li>
))}
</ul>
</main>
);
}
export async function getStaticProps() {
const articulos = await getArticulos();
return {
props: { articulos },
revalidate: 60, // ISR
};
}
2. Página de artículo (pages/articulo/[slug].js
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { getArticuloPorSlug, getArticulos } from "@/lib/api";
export default function Articulo({ articulo }) {
const { titulo, contenido } = articulo.attributes;
return (
<main className="p-8">
<h1 className="text-3xl font-bold mb-4">{titulo}</h1>
<article dangerouslySetInnerHTML= />
</main>
);
}
export async function getStaticPaths() {
const articulos = await getArticulos();
const paths = articulos.map(({ attributes }) => ({
params: { slug: attributes.slug },
}));
return { paths, fallback: 'blocking' };
}
export async function getStaticProps({ params }) {
const articulo = await getArticuloPorSlug(params.slug);
return {
props: { articulo },
revalidate: 60,
};
}
🖼️ Mostrar imágenes subidas a Strapi
Strapi devuelve las URLs relativas, así que necesitas concatenar el dominio:
1
2
const imagenUrl = articulo.attributes.imagen.data.attributes.url;
const urlAbsoluta = `http://localhost:1337${imagenUrl}`;
Y en JSX:
1
<img src={urlAbsoluta} alt="Imagen del artículo" />
🌍 Desplegar el frontend
Lo más fácil es usar Vercel:
1
npx vercel
- Añade como variable de entorno:
NEXT_PUBLIC_API_URL=https://tu-backend-strapi.com/api
- Vercel se encargará del resto ✨
🧪 Resultado final
- El contenido se crea en Strapi
- El frontend de Next.js lo consume y muestra
- Si cambias algo en Strapi, el frontend se actualiza automáticamente en el próximo build o al cabo de unos segundos si usas ISR (Incremental Static Regeneration)
🧭 Rutas posibles a futuro
Ahora que tengo el backend y el frontend conectados, puedo hacer cosas como:
- Crear un buscador
- Añadir una landing con filtros
- Mostrar categorías, etiquetas, fechas…
- Proteger rutas privadas (por rol)
- Mostrar previews para redacción de contenidos
🗒️ Notas personales
Conectar Strapi con Next.js ha sido bastante fluido. Me ha gustado que:
- Puedes usar
getStaticProps
y tener todo pre-renderizado - Los slugs se gestionan fácilmente
- La separación backend/frontend es muy clara y elegante
Y sobre todo: tengo control total, tanto del contenido como de la forma en que lo muestro. La arquitectura Headless empieza a cobrar sentido poco a poco 🧩