Introdução ao Next.js 15 e MDX: Como Começar

Configurar um blog com MDX pode parecer complicado no início, mas com as ferramentas certas, o processo se torna bem mais simples. Este guia vai direto ao ponto, mostrando como criar um blog funcional usando Next.js e MDX, aproveitando ao máximo o server-side rendering (SSR). O objetivo é apresentar um método eficiente para quem deseja começar a blogar sem complicações, desde a instalação até a publicação do conteúdo.

Instalação e Configuração Inicial

Vamos começar com a instalação básica do Next.js, seguindo as instruções fornecidas pela própria documentação. Em seguida, faremos alguns ajustes para otimizar o processo. Para criar um novo projeto Next.js, abra o terminal e navegue até a pasta onde você deseja criar o projeto. Execute o seguinte comando:

npx create-next-app@latest

Durante a configuração, você pode escolher as opções padrão para facilitar o acompanhamento deste guia. Certifique-se de selecionar Typescript, Tailwind CSS e o App Router do Next.js. O App Router é a API mais recente e será fundamental para utilizarmos o server-side rendering de forma eficaz. Após a criação do projeto, inicie o servidor de desenvolvimento e acesse http://localhost:3000 para verificar se tudo está funcionando corretamente.

npm run dev

Se preferir, você pode usar o pnpm em vez do npm, adicionando --use-pnpm ao final do comando. Embora o pnpm seja uma ótima opção, manteremos o npm para simplificar as coisas neste guia.

Configurando o Prettier (Opcional)

A configuração do Prettier é opcional, mas altamente recomendada para manter a consistência do código. O Prettier formata o código automaticamente, e o plugin do Tailwind CSS organiza as classes HTML de forma consistente. Para adicionar o Prettier, instale as dependências de desenvolvimento necessárias:

npm install --save-dev prettier prettier-plugin-tailwindcss eslint-plugin-prettier

Crie um arquivo chamado .prettierrc.json na raiz do projeto e adicione sua configuração preferida. Aqui está um exemplo de configuração:

{
  "plugins": ["prettier-plugin-tailwindcss"],
  "singleQuote": true,
  "jsxSingleQuote": true,
  "semi": false
}

Adicione um script ao seu package.json para formatar todo o projeto de uma vez:

{
  "scripts": {
    "format": "prettier --write ."
  }
}

Execute o script para formatar o projeto:

npm run format

Para configurar o ESLint para trabalhar com o Prettier, atualize o arquivo eslint.config.mjs para incluir as regras do Prettier:

const eslintConfig = [
  ...compat.extends(
    'next/core-web-vitals',
    'next/typescript',
    'plugin:prettier/recommended', // <---
  ),
]

Execute o ESLint para garantir que tudo está funcionando:

npm run lint

Integrando o Blog com MDX

Agora que o projeto está configurado, vamos instalar o MDX. A documentação do Next.js oferece excelentes detalhes para começar. Seguiremos essas instruções, importando os arquivos .mdx diretamente para os componentes.

Se você estiver carregando conteúdo markdown de uma fonte externa, considere usar o next-mdx-remote-client. Nesse caso, não será necessário configurar o Next.js para importar arquivos .mdx diretamente.

Instalação das Dependências do MDX

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

Configuração do Next.json para MDX

Atualize o arquivo next.config.ts para permitir a importação de arquivos markdown nos seus componentes JSX:

import { NextConfig } from 'next'
import createMDX from '@next/mdx'

const nextConfig: NextConfig = {
  // Configure `pageExtensions` to include markdown and MDX files
  pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
}

const withMDX = createMDX({
  extension: /\.mdx?$/,
})

// Merge MDX config with Next.js config
export default withMDX(nextConfig)

Configuração dos Componentes Globais do MDX

Crie um arquivo chamado mdx-components.tsx na raiz do projeto. Este arquivo permite injetar componentes globais que podem ser usados em qualquer arquivo MDX:

import type { MDXComponents } from 'mdx/types'

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return { ...components }
}

Adicionando Conteúdo em Markdown

Estamos quase lá. Agora, vamos usar dynamic imports para obter o markdown do sistema de arquivos, de forma similar à documentação do Next.js. Primeiro, adicione alguns arquivos markdown ao projeto.

Criando Arquivos MDX para as Publicações

Crie uma pasta chamada blogs dentro de src e adicione arquivos MDX com a extensão .mdx. Nomeie os arquivos como preferir, mas lembre-se que o nome do arquivo será usado como o slug da URL. Recomenda-se usar a convenção lower-kabob-case.

Aqui estão dois exemplos: um para ter uma ideia rápida de como a página será estilizada com vários elementos markdown, e outro simples para demonstrar várias páginas.

// src/blogs/markdown-test.mdx

export const metadata = {
  title: 'A test post',
  description: 'A post to test various markdown formatting',
  date: '2025-03-17T12:00:00Z',
  tags: ['Next.js', 'MDX', 'Typescript'],
}

## Heading 2

### Heading 3

#### Heading 4

A paragraph with some _emphasized_, **bolded**, and <s>strikethrough</s> text.

Unordered lists

- first
  - second
- third

Ordered lists

1. first
   1. second
2. third

> A quote!

[link to google](https://google.com)
// src/blogs/another-post.mdx

export const metadata = {
  title: 'Another test post',
  description: 'Demonstrating multiple posts',
  date: '2025-03-18T12:00:00Z',
  tags: ['writing'],
}

## Introduction

Hello!

Como você pode ver, também exportamos um objeto chamado metadata do arquivo. Poderemos usar isso, ou qualquer dado exportado dos arquivos MDX, no nosso código React.

Roteamento para as Páginas do seu Blog com MDX

Agora, vamos criar uma página no App Router para visualizar o conteúdo MDX. Dentro da pasta src/app, crie uma nova pasta chamada blogs e, dentro dela, uma pasta chamada [slug]. Lá, criaremos o arquivo page.tsx.

Aqui está um componente de página básico. Incluí uma seção de título que usa os metadados exportados e o próprio conteúdo markdown.

// src/app/blogs/[slug]/page.tsx

type BlogPageProps = {
  params: Promise<{ slug: string }>
}

type BlogPostMetadata = {
  title: string
  description: string
  date: string
  tags: string[]
}

export default async function BlogPage({ params }: BlogPageProps) {
  const { slug } = await params
  const post = await import(`@/blogs/${slug}.mdx`)

  // Get the react component from processing the MDX file
  const MDXContent = post.default

  // Process exported metadata to construct the title area of our blog post
  const metadata: BlogPostMetadata = post.metadata
  const title = metadata.title
  const date = new Date(metadata.date)
  const tags = metadata.tags
  const formattedDate = new Intl.DateTimeFormat('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
  }).format(date)

  return (
    <div className='flex flex-col items-center gap-6 py-6'>
      {/* some wrappers for styling and additional content*/}
      <div className='mx-auto w-full max-w-[768px]'>
        <article className='w-full p-6'>
          {/* A title section using the markdown metadata */}
          <div className='mt-6 mb-8'>
            <h1 className='mb-2 text-4xl font-bold'>{title}</h1>
            <div className='flex items-center gap-2 py-2'>
              <span className='text-sm'>{formattedDate}</span>|
              <div className='flex gap-1'>
                {tags.map((tag) => (
                  <span
                    key={tag}
                    className='border-foreground rounded-full border px-2 py-1 text-xs'
                  >
                    {tag}
                  </span>
                ))}
              </div>
            </div>
          </div>
          {/* The markdown content */}
          <MDXContent />
        </article>
      </div>
    </div>
  )
}

export function generateStaticParams() {
  // A list of params, which we will update shortly to use the file system.
  return [{ slug: 'markdown-test' }, { slug: 'another-post' }]
}

// By marking as false, accessing a route not defined in generateStaticParams will 404.
export const dynamicParams = false

A estrutura do seu projeto deve ser semelhante a esta:

project-root/
├── src/
│   ├── app/
│   │   ├── blogs/
│   │   │   └── [slug]/
│   │   │       └── page.tsx
│   │   ├── globals.css
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── blogs/
│       ├── markdown-test.mdx
│       └── another-post.mdx
├── mdx-components.tsx
├── ...

Testando a Configuração do Blog com MDX

Acesse http://localhost:3000/blogs/markdown-test no seu navegador para ver o resultado. Você deverá ver algo parecido com a imagem fornecida.

Estilizando o Conteúdo com Tailwind CSS

O CSS base do Tailwind não formata os elementos gerados pelo markdown. Para resolver isso, usaremos o plugin @tailwindcss/typography.

Instalando o Plugin Tailwind Typography

Adicione a dependência de desenvolvimento necessária:

npm install --save-dev @tailwindcss/typography

Importe os novos estilos no topo do arquivo globals.css:

@import 'tailwindcss';
@plugin "@tailwindcss/typography";

Aplicando a Formatação ao Conteúdo do Artigo

O plugin Typography fornece estilos para tudo dentro de uma tag com a classe prose. Adicione essa classe ao seu <article>:

        <article className='prose w-full p-6'>

Ajustando o Tema do Typography

O plugin oferece várias variáveis para personalizar o tema. Para este guia, adicionaremos algumas substituições simples. Sinta-se à vontade para ajustar esses valores e incorporá-los ao seu tema!

:root {
  /* ...
   * add an accent color for light-mode */
  --primary: var(--color-blue-800);
}

@theme inline {
  /* ...
    * register additional colors to use with Tailwind classes, e.g. `bg-primary` */
  --color-primary: var(--primary);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* ...
     * add an accent color for dark-mode */
    --primary: var(--color-blue-300);
  }
}

/* Overrides for Tailwind Typography */
article.prose {
  --tw-prose-body: var(--foreground);
  --tw-prose-headings: var(--primary);
  --tw-prose-lead: var(--foreground);
  --tw-prose-links: var(--primary);
  --tw-prose-bold: var(--foreground);
  --tw-prose-counters: var(--primary);
  --tw-prose-bullets: var(--primary);
  --tw-prose-hr: var(--foreground);
  --tw-prose-quotes: var(--foreground);
  --tw-prose-quote-borders: var(--foreground);
  --tw-prose-captions: var(--foreground);
  --tw-prose-kbd: var(--foreground);
  --tw-prose-code: var(--foreground);
  --tw-prose-pre-code: var(--foreground);
  --tw-prose-th-borders: var(--foreground);
  --tw-prose-td-borders: var(--foreground);
}

Agora você deverá ver um resultado mais estilizado, conforme a imagem fornecida.

Gerenciando Arquivos e Metadados no seu Blog com MDX

Vamos adicionar algumas funções utilitárias em um módulo dentro de src/lib. Isso nos ajudará a buscar as publicações do blog e seus metadados.

Obtendo uma Publicação Única com Metadados

Mova o tipo BlogPostMetadata para o novo arquivo e c

Leave a Comment