Cómo crear un blog con Nuxt, Content y Markdown
NuxtJS es una herramienta para crear aplicaciones web usando toda la potencia de Vue tanto en la aprte del cliente como en la del servidor.
Ha recibido inversiones millonarias y su ecosistema no para de crecer y mejorar.
Vemos en este tutorial una guía para empezar, en muy pocos pasos, un blog construido sobre ficheros Markdown.
Aquí puedes ver una demostración.
Y, al final del todo, los enlaces.
(Un secreto nada más empezar: también funciona para JSON, Yaml y CSV).
Eso si, hay tantas opciones posibles que en nuestros directos Live Coding hemos creado y desplegado una aplicación web completa con NuxtJS y Github Actions.
Muy pronto disponible en formato curso, apúntate para ser el primero en enterarte.
Instalación de Nuxt y el módulo Content ¶
El proceso de instalación de Nuxt es tan sencillo como ejecutar este comando:
npx create-nuxt-app nuxt-content-headless-blog
Donde nuxt-content-headless-blog
es el nombre de la carpeta donde se va a crear la app.
Debes tener instalado node
en tu ordenador. La versión con la que he trabajado para esta aplicación es la 13.11
.
Te hará varias preguntas en la instalación. Elige siempre las más sencillas.
Solo te hace falta una para que todo esto sea compatible con tu proyecto. Al preguntar por un framework CSS, elige Tailwind CSS.
Esto hará que tengas una configuración por defecto para cargar los ficheros de Tailwind.
Puedes arrancar la aplicación de demostración con:
cd nuxt-content-headless-blog
npm run dev
En la pantalla te aparecerá algo como esto y podrás acceder a tu entorno local de desarrollo.
Nos queda un paso muy importante.
Instalar el módulo oficial Content
, creado por el equipo de Nuxt. Una auténtica delicia.
npm install @nuxt/content
Tienes que agregar el registro del módulo en el archivo nuxt.config.js
{
modules: [
'@nuxt/content'
],
}
Ya lo tenemos todo listo.
Con esto acabas de dotar a Nuxt de la capacidad de generar una APi para convertir a tu aplicación en un Git-Headless CMS.
Crear el contenido en formato Markdown ¶
Markdown es un formato popularizado por Github que nos permite crear documentos con un marcado específico para que, al ser procesados, generen HTML que pueda entender el navegador.
Nuestros ficheros markdown tienen una peculiaridad, ya que contienen en su cabecera una sección de configuración llamada frontmatter
.
Un ejemplo para que veas:
---
title: Este es el título de tu contenido
description: Aquí una descripción
published: true
---
Esta estructura es libre, puedes crearla como quieras, aunque luego vas a tener que emplearla en el código.
Creamos los ficheros de contenido en la carpeta /content
. Puede modificarse mediante configuración, pero el módulo Content
buscará ahí por defecto.
Si te dijas en el repositorio en la carpeta /content
tenemos este esquema de contenido, que es el que vamos a emplear en este manual.
content
├── index.md
└── posts
├── ipsum.md
└── lorem.md
Crear nuestra primera página ¶
Nuxt necesita algo de código para saber que tiene que cargar en la página principal de la web.
Creamos el fichero /pages/index.vue
.
Esto va a decirle a Nuxt que estamos creando el contenido de la home que vas as poder ver en http://localhost:3000/
<template>
<div>
<h1 class='text-3xl py-6'>{{ index.title }}</h1>
<p class='text-xl py-3'>{{ index.description }}</p>
<nuxt-content :document='index' class='leading-loose' />
</div>
</template>
<script>
export default {
async asyncData({ $content, params, error }) {
const index = await $content('index')
.fetch()
.catch(err => {
error({ statusCode: 404, message: 'index not found' });
});
return {
index
};
}
};
</script>
Aquí pasan muchas cosas:
- Es la sintaxis que usamos en Vue, las páginas son componentes de VueJS pero con métodos hipervitaminados. Si no conoces Vue, tenemos un curso que puede venirte bien.
- Comenzamos por
<script>
. El métodoasyncData
es una de esas herramientas que nos provee Nuxt. Lo que pase dentro va a poder estar accesible en latemplate
sin tener que definir expresamente la propiedad. - La potencia está en
$content('index')
. Esta llamada es capaz de capturar el fichero que creamos antes en/content/index.md
y junto confetch()
tenerlo a nuestra disposición como un objeto con el que poder trabajar en JavaScript. - Que no se nos olvide el
return {index}
para tener accesible ese objeto en eltemplate
.
En la parte de la plantilla HTML puedes ver como usamos clases propias de la librería TailwindCSS, de la que hablamos en profundidad en un LiveCoding.
- ¿Recuerdas lo que te hablé más arriba del
frontmatter
? Pues eso nos facilita usar las variables{{ index.title }}
o{{ index.description }}
en nuestra plantilla. - El
nuxt-content
es un componente propio del móduloContent
al que hay que pasarle el documento que queremos procesar desde Markdown hacia HTML mediante el atributodocument
.
En este punto tenemos todas las piezas enganchadas.
Si accedes a la home de tu aplicación local, podrás ver el contenido de /content/index.md
pintado de forma preciosa.
Una página para cada post ¶
Un blog tiene que tener contenido. Cada artículo con su propio enlace.
Lo primero que te voy a contar es que la forma de estructurar las carpetas y nombres de Nuxt dentro de pages
es muy generosa.
Si creas un fichero pages\carpeta\fichero.vue
podrás acceder a la ruta /carpeta/fichero
, porque Nuxt se encarga de crearla para ti.
Un paso más allá: las rutas dinámicas.
Para ellas Nuxt utiliza un nombre de parámetro con un prefijo en forma de barra baja _
.
Así que vamos a crear el fichero pages/posts/_slug.vue
.
Cada vez que accedemos a /posts/aqui-va-el-slug
Nuxt intentará cargar su contenido en ese fichero recién creado, siendo slug
el parámetro dinámico que intentará buscar en algún sitio de content
un aqui-va-el-slug.md
.
Vamos a por ello. Creamos pages/posts/_slug.vue
.
<template>
<div>
<h1
class='my-8 max-w-full m-auto text-3xl text-center font-medium'
>
{{ post.title }}
</h1>
<h3 class='py-4 text-center uppercase'>{{ post.description }}</h3>
<nuxt-content :document='post' class='leading-loose' />
</div>
</template>
<script>
export default {
async asyncData({ $content, params, error }) {
const post = await $content(`posts/${params.slug}`)
.fetch()
.catch(err => {
error({ statusCode: 404, message: 'Página no encontrada' });
});
return {
post
};
}
};
</script>
Es muy parecido al caso de index.vue
pero con algunas particularidades.
- En
asyncData
ahora tenemos una llamada a$content(
posts/${params.slug})
. Enparams.slug
es donde tenemos ese alias de ruta que va a coincidir con el nombre de un archivo que estará en la carpeta/content/posts
. - Si te das cuenta
$content
funciona como una API, intentando capturar los datos pasándole una ruta concreta. Prueba a cargarhttp://localhost:3000/_content/
en tu navegador y la verás en marcha, gracias al móduloContent
. (Esto es lo que te contaba al principio de Git Headless CMS.) - Con
fetch()
convertimos el contenido en Markdown en algo que pueda entender JavaScript y lo almacenamos enpost
que estará disponible en nuestrotemplate
. - Si algo no funciona bien, capturamos el error y generamos una página de error 404.
El resto del código se parece mucho a lo que vimos en el paso anterior.
Listado de posts en la home ¶
Todo buen blog necesita de un listado de contenidos a los que poder acceder.
Hagámoslo aprovechando lo que nos ofrece el método fetch
de Nuxt.
Volvemos a \pages\index.vue
y modificamos su contenido.
<template>
<div>
<h1 class='text-3xl py-6'>{{ index.title }}</h1>
<p class='text-xl py-3'>{{ index.description }}</p>
<nuxt-content :document='index' class='leading-loose' />
<ul class='list-disc list-inside mb-4'>
<li v-for='(post, index) in posts' :key='index'>
<nuxt-link :to='post.path' class='underline'>{{ post.title }}</nuxt-link>
</li>
</ul>
</div>
</template>
<script>
export default {
async asyncData({ $content, params, error }) {
const index = await $content('index')
.fetch()
.catch(err => {
error({ statusCode: 404, message: 'index not found' });
});
const posts = await $content('posts')
.only(['title', 'path'])
.limit(5)
.sortBy('title')
.where({
published: true
})
.fetch()
.catch(err => {
error({ statusCode: 404, message: 'Página no encontrada' });
});
return {
index,
posts
};
}
};
</script>
Han aparecido cosas nuevas.
-
Empezamos por
script
como siempre. Ahora tenemos un$content('posts')
que tiene más modificadores de los que habíamos visto hasta ahora.- Solo queremos el
title
y elpath
y por eso usamosonly()
. Elpath
lo generaContent
por nosotros basándose en la ruta de los ficheros Markdown del blog. - Con
limit(5)
nos quedamos sólo con 5 resultados. - Los ordenamos alfabéticamente por el título con
sortBy('title')
. - Incluso podemos hacer una query gracias al método
where
. Así solo mostraremos los posts que tengan el atributopublished
atrue
en su cabecera.
- Solo queremos el
-
Con
posts
disponible en<template>
solo resta hacer un buclev-for
tan habitual en VueJS para generar una lista de enlaces.
Si accedes a tu home en local tendrás algo parecido a esto.
En el repositorio tienes más estilos y un toque de HTML para que todo quede más bonito. Puedes encontrarlos en /layout/default.vue
y /pages/posts/_slug.vue
Bola extra: Generar las rutas estáticas ¶
Una de las características de Nuxt es que puedes crear aplicaciones web que se ejecuten en el servidor y en el cliente.
Lo que le hace especial y fácil de desplegar es su capacidad de generar todo el contenido de forma estática. Igual que Jekyll, Gatsby, Eleventy y tantos otros.
Si lanzas este comando:
npm run generate
Se creará una carpeta /dist
con todo tu contenido en formato estático.
Puedes cargarlo en tu navegador directamente.
Pero, ¿qué pasa con los posts?
Aunque sean accesibles desde el listado, no están generados estáticamente. Solo dinámicamente. Si alguien accede por la ruta creada no será capaz de acceder.
Nuxt necesita que le digas donde están las rutas que tiene que generar.
Así que nos vamos a nuxt.config.js
y añadimos esta configuración, justo debajo del modules
que colocamos al comienzo de todo.
generate: {
async routes() {
const { $content } = require('@nuxt/content');
const files = await $content('posts')
.only(['path'])
.fetch();
return files.map(file => (file.path === '/index' ? '/' : file.path));
}
},
Si vuelves a lanzar npm run generate
verás como ahora si se generan las rutas de los posts que estaban pendientes.
¡Ya tienes tu blog preparado y listo!
Enlaces del tutorial ¶
- Repositorio de código del manual
- Demostración
- Aprende Nuxt en la Zona Premium
- TailwindCSS y VueJS en la Zona Premium
- Nuxt
- Módulo Content
- Markdown aleatorio generado en Markdownum
- Estructura de Tailwind generada en Stitches
Si quieres seguir aprendiendo, puedes hacerlo en mi Curso gratis.
Escrito por:
Daniel Primo
12 recursos para developers cada domingo en tu bandeja de entrada
Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.