CÓMO CONSTRUIR UN BLOG CON NEXT.JS Y MDX
Introducción a la creación de un blog con Next.js y MDX
Crear un blog técnico enfocado en programación y noticias de tecnología requiere herramientas que ofrezcan flexibilidad, rendimiento y facilidad para gestionar contenido. Next.js, un framework basado en React, se ha convertido en una opción popular para desarrolladores que buscan construir sitios web dinámicos y optimizados. Al integrarlo con MDX, una extensión de Markdown que permite incluir componentes de React, puedes personalizar tu blog con código reutilizable y mantener un flujo de trabajo eficiente. Este tutorial te guiará paso a paso para construir un blog utilizando Next.js y MDX, desde la configuración inicial hasta la publicación de artículos con un diseño optimizado para SEO y rendimiento. A lo largo del proceso, se abordarán herramientas modernas y soluciones a problemas comunes, con ejemplos prácticos de código.
Por qué elegir Next.js y MDX
Next.js es un framework versátil que combina renderizado estático, renderizado del lado del servidor y una experiencia de desarrollo fluida. Su compatibilidad con MDX permite a los desarrolladores escribir contenido en Markdown mientras incrustan componentes de React directamente en los archivos, lo que reduce la necesidad de código repetitivo. Esta combinación es ideal para blogs de tecnología, ya que facilita la creación de páginas dinámicas, como listas de artículos y rutas únicas para cada publicación, al tiempo que mantiene un alto nivel de personalización. Además, Next.js ofrece optimización automática de imágenes y soporte nativo para SEO, lo que es crucial para sitios que buscan destacar en motores de búsqueda.
Por ejemplo, un archivo MDX puede contener tanto texto en Markdown como componentes personalizados:
---
title: "Optimización de imágenes en Next.js"
publishedAt: "2025-10-24"
excerpt: "Cómo usar el componente Image de Next.js para mejorar el rendimiento."
---
# Título del artículo
Este es un párrafo en Markdown. A continuación, un componente personalizado:
<Image src="/ruta/a/imagen.jpg" alt="Optimización de imágenes" />
Configuración inicial del proyecto
Para comenzar, crea un nuevo proyecto de Next.js. Abre tu terminal y ejecuta el siguiente comando para generar una aplicación base:
npx create-next-app@latest blog
cd blog
Esto genera una estructura de carpetas básica con archivos esenciales. Los directorios más relevantes para este tutorial son pages y src/utils, donde configuraremos las rutas dinámicas y las utilidades para procesar archivos MDX. La estructura inicial debería verse así:
|-- pages
| |-- blog
| | |-- index.js
| | |-- [slug].js
| |-- _app.js
| |-- index.js
|-- src
| |-- utils
| |-- mdx.js
|-- data
| |-- articles
| |-- ejemplo-post.mdx
Instala las dependencias necesarias para manejar archivos MDX y procesar contenido:
npm install gray-matter reading-time next-mdx-remote glob dayjs
- gray-matter: Procesa metadatos y contenido de archivos MDX.
- reading-time: Calcula el tiempo estimado de lectura basado en el conteo de palabras.
- next-mdx-remote: Compila archivos MDX para su uso en Next.js.
- glob: Permite buscar archivos con patrones específicos, como los artículos en
data/articles. - dayjs: Gestiona y formatea fechas para los metadatos de los artículos.
Estructura de los archivos MDX
Los archivos MDX contienen metadatos (frontmatter) y contenido en Markdown. Crea un directorio data/articles y añade un archivo de ejemplo, como ejemplo-post.mdx:
---
title: "Introducción a Next.js y MDX"
publishedAt: "2025-10-24"
excerpt: "Aprende a construir blogs dinámicos con Next.js y MDX."
cover_image: "/images/portada.jpg"
---
# Bienvenidos al blog
Este es un artículo de ejemplo que combina Markdown con componentes de React.
El frontmatter incluye información como el título, la fecha de publicación y un extracto, que se usarán para mostrar los artículos en la página principal del blog.
Procesamiento de archivos MDX
En el directorio src/utils, crea un archivo mdx.js para definir funciones que lean y procesen los archivos MDX. Estas funciones aprovecharán el API de FileSystem de Node.js y las dependencias instaladas.
Obtener slugs de los artículos
La primera función, getSlug, genera una lista de slugs (identificadores únicos) basados en los nombres de los archivos MDX:
import path from "path";
import { sync } from "glob";
const articlesPath = path.join(process.cwd(), "data/articles");
export async function getSlug() {
const paths = sync(`${articlesPath}/*.mdx`);
return paths.map((path) => {
const pathContent = path.split("/");
const fileName = pathContent[pathContent.length - 1];
const [slug] = fileName.split(".");
return slug;
});
}
Esta función utiliza glob para encontrar todos los archivos .mdx en data/articles y extrae el slug eliminando la extensión del archivo.
Obtener contenido de un artículo
La función getArticleFromSlug lee el contenido y los metadatos de un artículo específico:
import fs from "fs";
import matter from "gray-matter";
import readingTime from "reading-time";
export async function getArticleFromSlug(slug) {
const articleDir = path.join(articlesPath, `${slug}.mdx`);
const source = fs.readFileSync(articleDir);
const { content, data } = matter(source);
return {
content,
frontmatter: {
slug,
excerpt: data.excerpt,
title: data.title,
publishedAt: data.publishedAt,
readingTime: readingTime(source).text,
...data,
},
};
}
Aquí, fs.readFileSync lee el archivo de forma síncrona, y gray-matter extrae el contenido y los metadatos. El tiempo de lectura se calcula con readingTime.
Listar todos los artículos
La función getAllArticles recopila todos los artículos para mostrarlos en la página principal del blog:
export async function getAllArticles() {
const articles = fs.readdirSync(articlesPath);
return articles.reduce((allArticles, articleSlug) => {
const source = fs.readFileSync(
path.join(articlesPath, articleSlug),
"utf-8"
);
const { data } = matter(source);
return [
{
...data,
slug: articleSlug.replace(".mdx", ""),
readingTime: readingTime(source).text,
},
...allArticles,
];
}, []);
}
Esta función lee todos los archivos en data/articles, procesa sus metadatos y devuelve un array con la información de cada artículo.
Mostrar la lista de artículos
En pages/blog/index.js, utiliza getStaticProps para cargar los artículos y renderizarlos en la página principal del blog:
import { getAllArticles } from "../../src/utils/mdx";
import Link from "next/link";
import dayjs from "dayjs";
export default function BlogPage({ posts }) {
return (
<>
<h1>Blog de Tecnología</h1>
<div>
{posts.map((post) => (
<Link href={`/blog/${post.slug}`} key={post.slug} passHref>
<div>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<p>
{dayjs(post.publishedAt).format(
"D [de] MMMM [de] YYYY"
)}{" "}
— {post.readingTime}
</p>
</div>
</Link>
))}
</div>
</>
);
}
export async function getStaticProps() {
const articles = await getAllArticles();
articles.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
return {
props: {
posts: articles,
},
};
}
La función getStaticProps ordena los artículos por fecha de publicación (de más reciente a más antiguo) y los pasa como props al componente.
Renderizar artículos individuales
Para mostrar un artículo específico, configura el archivo pages/blog/[slug].js con las funciones getStaticPaths y getStaticProps:
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import dayjs from "dayjs";
import rehypeSlug from "rehype-slug";
import rehypeHighlight from "rehype-highlight";
import rehypeCodeTitles from "rehype-code-titles";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import { getSlug, getArticleFromSlug } from "../../src/utils/mdx";
import "highlight.js/styles/atom-one-dark-reasonable.css";
export default function BlogPost({ post: { source, frontmatter } }) {
return (
<>
<h1>{frontmatter.title}</h1>
<p>
{dayjs(frontmatter.publishedAt).format("D [de] MMMM [de] YYYY")}{" "}
— {frontmatter.readingTime}
</p>
<div>
<MDXRemote {...source} />
</div>
</>
);
}
export async function getStaticPaths() {
const paths = (await getSlug()).map((slug) => ({ params: { slug } }));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const { slug } = params;
const { content, frontmatter } = await getArticleFromSlug(slug);
const mdxSource = await serialize(content, {
mdxOptions: {
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{ behavior: "wrap", properties: { className: ["anchor"] } },
],
rehypeHighlight,
rehypeCodeTitles,
],
},
});
return {
props: {
post: {
source: mdxSource,
frontmatter,
},
},
};
}
Instala los plugins de rehype para mejorar el renderizado del contenido:
npm install rehype-highlight rehype-autolink-headings rehype-code-titles rehype-slug
Estos plugins añaden resaltado de sintaxis, enlaces automáticos a encabezados, títulos para bloques de código y slugs para los encabezados.
Configuración de next.config.js
Configura el archivo next.config.js para evitar errores de compatibilidad y optimizar imágenes:
module.exports = {
reactStrictMode: true,
images: {
loader: "akamai",
path: "",
},
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
"react/jsx-runtime.js": require.resolve("react/jsx-runtime"),
};
config.resolve.fallback = {
...config.resolve.fallback,
child_process: false,
fs: false,
};
return config;
},
};
Este archivo asegura que las imágenes se optimicen correctamente y resuelve problemas relacionados con módulos de Node.js en el entorno del navegador.
Personalización con componentes MDX
Puedes crear componentes personalizados en data/components/mdx-components.js para usarlos en tus archivos MDX:
import Image from "next/image";
export const SectionTitle = ({ children }) => (
<h2 className="section-title">{children}</h2>
);
export const Text = ({ children }) => <p className="text">{children}</p>;
export const components = {
Image,
SectionTitle,
Text,
};
Luego, pásalos al componente <MDXRemote>:
import { components } from "../../data/components/mdx-components";
<MDXRemote {...source} components={components} />;
Esto permite estilizar elementos específicos en tus artículos sin repetir código.
Optimización para SEO
Next.js facilita la optimización para motores de búsqueda mediante el componente <Head>. Agrega metadatos dinámicos en pages/blog/[slug].js:
import Head from "next/head";
export default function BlogPost({ post: { source, frontmatter } }) {
return (
<>
<Head>
<title>{frontmatter.title} | Blog de Tecnología</title>
<meta name="description" content={frontmatter.excerpt} />
</Head>
{/* resto del componente */}
</>
);
}
Esto mejora la visibilidad de tu blog en motores de búsqueda al proporcionar títulos y descripciones relevantes.
Resolución de problemas comunes
Si encuentras errores como “module not found” al usar next-mdx-remote, instala la dependencia como legacy peer:
npm install next-mdx-remote --legacy-peer-deps
Otro problema común es el error de optimización de imágenes en plataformas como Netlify. Asegúrate de configurar el loader de imágenes en next.config.js, como se mostró anteriormente.
Conclusiones
Construir un blog con Next.js y MDX ofrece una solución poderosa para desarrolladores que buscan flexibilidad y rendimiento. La combinación de renderizado estático, componentes reutilizables y soporte nativo para SEO hace que esta tecnología sea ideal para sitios de programación y noticias tecnológicas. A través de este tutorial, has aprendido a configurar un proyecto, procesar archivos MDX, mostrar listas de artículos y renderizar publicaciones individuales con personalizaciones avanzadas. Además, la integración de plugins como rehype y la correcta configuración de next.config.js aseguran un flujo de trabajo eficiente y un sitio optimizado. Con estas herramientas, puedes crear un blog moderno que no solo sea funcional, sino también atractivo para los lectores y los motores de búsqueda.