Cómo crear un blog en NextJS 13.4 con MDX !
toscadev
@ ToscaDevsJS
En este artículo, te guiaré a través de los pasos para crear tu propio blog MDX, aprovechando al máximo Contentlayer, remark y rehype plugins.
¿Qué es MDX y por qué usar?
MDX es una combinación de Markdown y JSX. Esto significa que puedes escribir contenido usando la sintaxis simple de Markdown, pero también tienes la capacidad de incorporar componentes JSX de React.
¿Qué es Contentlayer?
Esta biblioteca se centra en permitir a los desarrolladores trabajar con contenido estructurado de una manera más eficiente y organizada. Contentlayer puede convertir archivos Markdown y MDX en objetos de datos utilizables en tu aplicación.
Paso 1: Configuración del entorno de desarrollo.
npx create-next-app@latest
npm install contentlayer next-contentlayer
Paso 2: Configuración de archivos de NextJS.
next.config.js
const { withContentlayer } = require("next-contentlayer");
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = withContentlayer(nextConfig);
tsconfig.json
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
]
}
Paso 3: En la raíz de tu proyecto creamos un archivo llamado contentlayer.config.ts.
contentlayer.config.ts
contentlayer.config.ts
import { defineDocumentType, makeSource } from "contentlayer/source-files";
import remarkGfn from "remark-gfm";
import rehypePrismPlus from "rehype-prism-plus";
const Post = defineDocumentType(() => ({
name: "Post",
filePathPattern: `**/*.mdx`,
contentType: "mdx",
fields: {
title: {
type: "string",
description: "The title of the post",
required: true,
},
date: {
type: "date",
description: "The date of the post",
required: true,
},
asunto: {
type: "string",
description: "The asunto of the post",
required: true,
},
},
computedFields: {
url: {
type: "string",
resolve: (doc) => `/posts/${doc._raw.flattenedPath}`,
},
},
}));
export default makeSource({
contentDirPath: "posts",
documentTypes: [Post],
mdx: {
remarkPlugins: [remarkGfn],
rehypePlugins: [
[rehypePrismPlus, { defaultLanguage: "js", ignoreMissing: true }],
],
},
});
Paso 4: Creamos los archivos mdx y definimos su esquema.
@/posts/archivo.mdx
App
/layout.tsx/* { archivo.mdx */
---
title: Click me!
date: 2022-02-28
asunto: Lorem Ipsum es simplemente el texto de relleno de las imprentas y archivos de texto. Lorem Ipsum ha sid
---
Lorem Ipsum es simplemente el texto de relleno de las imprentas y archivos de texto. Lorem Ipsum ha sid
Paso 5: Creando ruta dinámica.
app/blog/posts/[slug]/page.tsximport { format, parseISO } from "date-fns";
import { allPosts, Post } from "contentlayer/generated";
import { getMDXComponent, useMDXComponent } from "next-contentlayer/hooks";
/*** */
import type { MDXComponents } from "mdx/types";
import "@/styles/mdx.css";
const mdxComponents: MDXComponents = {};
/*** */
export const generateStaticParams = async () =>
allPosts.map((post) => ({ slug: post._raw.flattenedPath }));
export const generateMetadata = ({ params }: { params: { slug: string } }) => {
const post: Post | undefined = allPosts.find(
(post) => post._raw.flattenedPath === params.slug
);
if (!post) throw new Error(`Publicar con slug ${params.slug} extraviado.`);
return { title: post.title };
};
const PostLayout = ({ params }: { params: { slug: string } }) => {
const post = allPosts.find((post) => post._raw.flattenedPath === params.slug);
if (!post) throw new Error(`Publicar con slug ${params.slug} extraviado.`);
const Content = getMDXComponent(post.body.code);
const MDXContent = useMDXComponent(post.body.code);
return (
<>
<main className="container relative ">
<section>
<time
dateTime={post.date}
className="block text-sm text-muted-foreground"
>
{format(parseISO(post.date), "LLLL d, yyyy")}
</time>
<h1 className="m-2 inline-blocktext-4xl leading-tight lg:text-5xl">
{post.title}
</h1>
<br />
<Content components={mdxComponents} />
{/* <MDXContent components={mdxComponents} /> */}
</section>
</main>
</>
);
};
export default PostLayout;
Paso 6: Listar artículos del blog.
app/blog/posts/page.tsxApp
/layout.tsximport { compareDesc, format, parseISO } from "date-fns";
import { allPosts, Post } from "contentlayer/generated";
function PostCard(post: Post) {
return (
<a href={post.url}>
<article className="p-4 md:p-8">
<time dateTime={post.date}>
{format(parseISO(post.date), "LLLL d, yyyy")}
</time>
<p>{post.title}</p>
<p>{post.asunto}</p>
</article>
</a>
);
}
export default function Home() {
const posts = allPosts.sort((a, b) =>
compareDesc(new Date(a.date), new Date(b.date))
);
return (
<section>
{posts.slice(0, 2).map((post, idx) => (
<PostCard key={idx} {...post} />
))}
</section>
);
}
FINALIZADO
NOTAS :.
./contentlayer.config.tsnpm install rehype-prism-plus
import rehypePrismPlus from "rehype-prism-plus";
npm install remark-gfm
import remarkGfn from "remark-gfm";
./contentlayer.config.ts
import rehypePrismPlus from "rehype-prism-plus";
import remarkGfn from "remark-gfm";
export default makeSource({
contentDirPath: "posts",
documentTypes: [Post],
mdx: {
remarkPlugins: [remarkGfn],
rehypePlugins: [
[rehypePrismPlus, { defaultLanguage: "js", ignoreMissing: true }],
],
},
});
HOOKS.
useMDXComponent es más eficiente si solo necesita renderizar un componente MDX una vez.
getMDXComponent es más eficiente si necesita renderizar el mismo componente MDX varias veces.
app/blog/posts/[...slug]/page.tsx
App
/blog/posts/[...slug]/page.tsximport "@/styles/mdx.css";
import btnNew from "@/comonentets/btnNew"
import { useMDXComponent, getMDXComponent } from "next-contentlayer/hooks";
import type { MDXComponents } from "mdx/types";
const mdxComponents: MDXComponents = {btnNew}; ===> Se añade el componete btnNew a al archivo MDX
const PostLayout = () => {
const Content = getMDXComponent(post.body.code);
const MDXContent = useMDXComponent(post.body.code);
return (
<Content components={mdxComponents} /> // ====> Cargando MDX más mdxComponents componetes reactJS.
{/* <MDXContent components={mdxComponents} /> */}
);
};
export default PostLayout;
Si te gusto esta guía no dudes en compartirla o sugerir tu opinión. ❤️