1. Home
  2. ChevronRightFeatures
  3. ChevronRightColecciones

Colecciones

Las colecciones te permiten agrupar documentos relacionados bajo directorios dinámicos con generación automática de rutas. Al definir convenciones basadas en carpetas, puedes desacoplar las páginas de documentación estándar de listas personalizadas, páginas de publicaciones y diseños personalizados.

Inicio RápidoLink

Crear una nueva colección solo requiere dos pasos:

1. Crea una carpeta entre corchetesLink

Agrega un directorio que comience con [ y termine con ] dentro de tu directorio docs/ y coloca tus archivos MDX/Markdown dentro.

mkdir -p docs/docs/\[blog\]
touch docs/docs/\[blog\]/first-post.mdx

2. Agrega FrontmatterLink

Agrega metadatos en la parte superior de tus archivos markdown. Boltdocs registrará automáticamente estos archivos como publicaciones de colección.

---
title: "Introducing Boltdocs Collections"
date: 2026-05-28
author: Jesús Alcalá
excerpt: "A look at the new flexible collection routing."
---

Welcome to the future of dynamic collections in Boltdocs!

¡Listo! Tu publicación será accesible en /blog/first-post y un índice de lista de colección se generará automáticamente en /blog.


Cómo FuncionaLink

Boltdocs interpreta los nombres de directorios entre corchetes (por ejemplo [blog], [releases]) como colecciones.

docs/
└── docs/
    └── [blog]/                  → Espacio de Nombres de Colección Dinámica
        ├── list.tsx             → Diseño Personalizado del Índice de Colección (Opcional)
        ├── post.tsx             → Diseño Personalizado de Vista de Publicación (Opcional)
        ├── layout.tsx           → Diseño Raíz Personalizado para esta Colección (Opcional)
        ├── first-post.mdx       → Página de publicación en `/blog/first-post`
        └── second-post.mdx      → Página de publicación en `/blog/second-post`

Reglas de Convención de Carpetas:Link

  1. Espacio de Nombres Dinámico: El nombre de la carpeta entre corchetes (por ejemplo [blog]) se mapea al ID de colección (blog) y sirve como ruta base de URL /blog.
  2. Sobrescritura de Lista (list.tsx): Si existe, sobrescribe la página de listado de índice en /blog. Recibe la lista de todas las publicaciones en la colección.
  3. Sobrescritura de Publicación (post.tsx): Si existe, sobrescribe el diseño contenedor para publicaciones individuales (por ejemplo /blog/first-post).
  4. Sobrescritura de Diseño (layout.tsx): Si existe, actúa como el diseño contenedor de la ruta raíz (con su propia barra lateral o contexto de navegación) para todas las rutas dentro de la colección.

Diseño de Vistas PersonalizadasLink

Para sobrescribir la apariencia predeterminada de tus colecciones, puedes colocar componentes React estándar directamente en la carpeta de la colección.

Componente de Publicación Personalizado (post.tsx)Link

Un componente de publicación personalizado te permite diseñar cómo se renderizan las entradas individuales (como un artículo de blog). Usa usePost() para acceder a los datos de la publicación actual — sin necesidad de useLoaderData:

import { usePost, useMergedComponents } from 'boltdocs/client'

export default function BlogPost({ MDXComponent, mdxComponents }: any) {
  const post = usePost()
  if (!post) return null

  const { title, date, author, excerpt, lastUpdated, coverImage } = post

  const allComponents = useMergedComponents(mdxComponents)
  const { LastUpdated } = allComponents

  return (
    <article className="max-w-3xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
      <header className="mb-10 pb-8 border-b border-gray-200 dark:border-gray-800">
        {title && (
          <h1 className="text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white mb-4">
            {title}
          </h1>
        )}

        <div className="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400 mt-6">
          {date && (
            <time dateTime={new Date(date).toISOString()}>
              {new Date(date).toLocaleDateString(undefined, {
                year: 'numeric',
                month: 'long',
                day: 'numeric',
              })}
            </time>
          )}

          {author && (
            <>
              <span aria-hidden="true">&middot;</span>
              <img
                src={typeof author === 'string' ? author : author.avatar}
                alt={typeof author === 'string' ? author : author.name}
                className="w-8 h-8 rounded-full"
              />
              <span>{typeof author === 'string' ? author : author.name}</span>
            </>
          )}
        </div>

        {coverImage && (
          <div className="relative aspect-video w-full overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800 bg-neutral-100 dark:bg-neutral-900 mt-8 mb-6">
            <img
              src={coverImage}
              alt={title || 'Cover image'}
              className="object-cover w-full h-full"
            />
          </div>
        )}

        {excerpt && (
          <p className="mt-6 text-xl text-gray-600 dark:text-gray-300">
            {excerpt}
          </p>
        )}
      </header>

      <div className="prose prose-blue dark:prose-invert max-w-none">
        <MDXComponent components={allComponents} />
      </div>

      {lastUpdated && LastUpdated && (
        <div className="mt-12 pt-8 border-t border-gray-200 dark:border-gray-800">
          <LastUpdated date={lastUpdated} />
        </div>
      )}
    </article>
  )
}
Info
Note

usePost() llamado sin parámetros retorna los datos de la publicación actual cuando se usa dentro de un componente post.tsx. Lee desde un contexto interno — sin necesidad de imports de useLoaderData o React Router.


Referencia de APILink

Estructuras de Datos del CargadorLink

Cuando escribes archivos list.tsx o post.tsx personalizados, Boltdocs proporciona hooks (usePosts, usePost) que manejan el acceso a datos internamente. Las estructuras de datos a continuación son lo que los cargadores de React Router retornan, pero deberías usar los hooks en su lugar:

CollectionPostLoaderDataLink

Retornado por los cargadores de React Router para rutas de publicaciones de colección. Usa usePost() en vez de acceder directamente.

PropiedadTipoPredeterminadoDescripción
routeComponentRouteMetadatos detallados de la ruta extraídos del frontmatter de la publicación.
collectionstringEl nombre de la colección a la que pertenece esta publicación (por ejemplo 'blog').
headingsHeading[][]Encabezados de página extraídos para generar tabla de contenido dinámica (TOC).

CollectionListLoaderDataLink

Retornado por los cargadores de React Router para índices de listas de colección (por ejemplo /blog). Usa useCollectionList() en vez de acceder directamente.

PropiedadTipoPredeterminadoDescripción
postsCollectionPostItem[][]Arreglo paginado de todas las publicaciones en la colección.
totalPagesnumber1Número total de páginas basado en la cantidad de publicaciones.
currentPagenumber1Número de página actual (índice basado en 1).
collectionstringEl nombre del identificador de la colección.

HooksLink

Boltdocs proporciona varios hooks de React bajo boltdocs/client para obtener tus colecciones desde cualquier parte de tu aplicación (como barras laterales, pies de página o páginas de inicio personalizadas).

Todos los hooks usan la colección "blog" por defecto cuando no se especifica ninguna.

usePosts(collection?: string, options?: { includeDrafts?: boolean })Link

Retorna un arreglo de todas las publicaciones en una colección, filtradas por la locale y versión actuales. Por defecto usa "blog". Usa esto para listas, barras laterales o cualquier componente que necesite publicaciones de colección.

import { usePosts } from 'boltdocs/client'

// Por defecto usa la colección "blog"
function BlogSidebar() {
  const posts = usePosts()
  return (
    <ul>
      {posts.map(post => <li key={post.path}>{post.title}</li>)}
    </ul>
  )
}

// Colección explícita
function ChangelogList() {
  const posts = usePosts('changelog')
  // ...
}

El arreglo retornado incluye todas las publicaciones filtradas — implementa tu propia paginación o scroll infinito haciendo slice del arreglo:

function PaginatedBlog() {
  const allPosts = usePosts()
  const [page, setPage] = useState(1)
  const perPage = 10
  const posts = allPosts.slice((page - 1) * perPage, page * perPage)
  // ...
}

Cuando los borradores son visibles (vía drafts.visible: true o BOLTDOCS_DRAFTS=true), las publicaciones en borrador se incluyen en los resultados. Para incluir o excluir borradores explícitamente:

// Incluir publicaciones en borrador (útil para paneles de administración)
function AdminBlogList() {
  const posts = usePosts('blog', { includeDrafts: true })
  return posts.map(post => (
    <div key={post.path}>
      {post.title}
      {post.draft && <span className="badge">Borrador</span>}
    </div>
  ))
}

// Excluir borradores explícitamente (comportamiento por defecto)
function PublicBlogList() {
  const posts = usePosts('blog', { includeDrafts: false })
  // ...
}

usePost()Link

Retorna los datos de la publicación actual cuando se llama dentro de un componente post.tsx. Sin parámetros necesarios — el hook lee desde un contexto interno provisto por el framework.

import { usePost } from 'boltdocs/client'

export default function BlogPost({ MDXComponent, mdxComponents }) {
  const { title, date, author, headings, lastUpdated } = usePost()
  // ...
}

También puedes llamar usePost(collection, slug) con parámetros explícitos para buscar una publicación específica desde cualquier parte de tu aplicación:

import { usePost } from 'boltdocs/client'

function FeaturedPost() {
  const post = usePost('blog', 'boltdocs-2.9.0')
  return <div>{post?.title}</div>
}

useRecentPosts(collection?: string, count?: number)Link

Retorna las publicaciones más recientes de una colección. Por defecto usa colección "blog" y cantidad de 5.

import { useRecentPosts } from 'boltdocs/client'

// Por defecto "blog", retorna las 3 más recientes
function RecentUpdates() {
  const recent = useRecentPosts('blog', 3)
  // ...
}

ComponentRouteLink

El tipo de mapeo de metadatos extraídos del frontmatter de tus archivos.

PropiedadTipoPredeterminadoDescripción
titlestring''Título de la publicación, obtenido del frontmatter title.
datestring | DateundefinedFecha de publicación, obtenida del frontmatter date.
authorstring | AuthorObjectundefinedDetalles del autor, soporta cadenas simples u objetos con name y avatar.
excerptstring''Descripción corta o extracto.
lastUpdatedstring | numberundefinedLa marca de tiempo del último commit de git (o override manual).
frontmatterRecord<string, any>{}Objeto extensible con todos los parámetros de frontmatter personalizados.

Personalización AvanzadaLink

Lista Personalizada (list.tsx)Link

Crea un archivo list.tsx dentro de tu carpeta de colección para sobrescribir la página de listado predeterminada. Usa usePosts() para obtener todas las publicaciones filtradas y maneja la paginación:

docs/docs/[blog]/list.tsx
import { useState } from 'react'
import { usePosts } from 'boltdocs/client'

export default function BlogList() {
  const allPosts = usePosts()
  const [page, setPage] = useState(1)
  const perPage = 10
  const posts = allPosts.slice((page - 1) * perPage, page * perPage)
  const totalPages = Math.ceil(allPosts.length / perPage)

  return (
    <div className="py-8 max-w-2xl mx-auto px-4">
      <h1 className="text-3xl font-bold mb-6">Blog</h1>

      <div className="space-y-8">
        {posts.map(post => (
          <article key={post.path} className="border-b border-subtle pb-6">
            <h2 className="text-xl font-semibold mb-2">
              <a href={post.path} className="text-primary-600 hover:underline">
                {post.title}
              </a>
            </h2>
            {post.date && (
              <time className="text-xs text-muted block mb-2">
                {new Date(post.date).toLocaleDateString()}
              </time>
            )}
            {post.excerpt && <p className="text-sm text-body">{post.excerpt}</p>}
          </article>
        ))}
      </div>

      {totalPages > 1 && (
        <div className="mt-8 flex gap-4 text-sm">
          {page > 1 && (
            <button onClick={() => setPage(page - 1)} className="text-primary-600 hover:underline">
              Anterior
            </button>
          )}
          <span>
            Página {page} de {totalPages}
          </span>
          {page < totalPages && (
            <button onClick={() => setPage(page + 1)} className="text-primary-600 hover:underline">
              Siguiente
            </button>
          )}
        </div>
      )}
    </div>
  )
}

Layout Personalizado (layout.tsx)Link

Crea un archivo layout.tsx dentro de tu carpeta de colección para envolver todas las rutas (lista y publicaciones) con un diseño personalizado. Esto es útil para agregar navegación específica de colección, barras laterales o encabezados:

docs/docs/[blog]/layout.tsx
import { usePosts } from 'boltdocs/client'

export default function BlogLayout({ children }: { children: React.ReactNode }) {
  const posts = usePosts()

  return (
    <div className="flex">
      <aside className="w-64 border-r border-subtle p-4">
        <h2 className="font-bold mb-4">Blog</h2>
        <nav>
          {posts.map(post => (
            <a
              key={post.path}
              href={post.path}
              className="block py-1 text-sm hover:text-primary-600"
            >
              {post.title}
            </a>
          ))}
        </nav>
      </aside>
      <main className="flex-1">{children}</main>
    </div>
  )
}

PaginaciónLink

Por defecto, cada página muestra 10 publicaciones, pero puedes configurar esto globalmente mediante collections.postsPerPage en tu boltdocs.config.ts:

boltdocs.config.ts
export default defineConfig({
  collections: {
    postsPerPage: 12, // Personaliza elementos por página
  },
})
Info
Acceso a Metadatos

Todos los parámetros escritos en el frontmatter de tu publicación se preservan en la propiedad de registro frontmatter dentro de route. Esto significa que puedes agregar banderas personalizadas (como featured: true o readingTime: '5 min') y consumirlas de forma segura en tus componentes personalizados.

Last updated on July 27, 2026

Was this page helpful?