Acceso

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.tsx
import { 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.tsx
App
/layout.tsx
import { 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.ts
npm 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.tsx
import "@/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. ❤️


Regresar al Blog.

© Copyright 2024 ToscaDevJS.