Workshop - Paso 3
Bueno, empecemos a pensar y hacer nuestro blog, qué emoción 😱🎉
Para nuestro blog vamos a escribir los posts en archivos MD
(Markdown),
porque es un formato bastante amigable y se puede traducir a HTML de forma
fácil. De más está decir que para transformar archivos MD a HTML tenemos un
plugin, no? 😎 Así que empecemos por ahí, vamos a generar archivos .md, crear
la fuente de datos usando un plugin source (el mismo que se usa ahora para las
imágenes 😉) para que luego con el plugin de tipo transformer podamos acceder
al contenido del archivo y nos convierta la data en algo amigable (HTML)
mientras estamos consultando en el frontend usando GraphQL 😊
Si algo no se entendió, recomiendo releer estos últimos renglones, y hasta podrían intentar hacerlo solos si se sienten confiados. Igual, si siguen leyendo, lo hacemos! 💪 Empecemos por buscar un plugin para manipular los archivos Markdown y lo instalamos, encontramos este plugin gatsby-transformer-remark, frenemos el servidor local y lo instalamos así:
npm i gatsby-transformer-remarkMientras se instala, preparemos nuestro plugin plugin-source-filesystem para
levantar nuestros posts de una carpeta local y que sean consultables. Creemos
una carpeta dentro de /src, a la misma altura que components, pages,
etc. y llamémosla posts por ejemplo. Ahora configuremos nuestro plugin igual a
como está para las imágenes, pero para posts y ya que estamos, podemos agregar
gatsby-transformer-remark:
//En gatsby-config.js
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
`gatsby-transformer-remark`,Genial! Con esto ya tenemos configurados los plugins, el de tipo source nos
permite ver qué archivos están en esa carpeta posts y automáticamente el de
tipo transformer se va a encargar de agarrar los archivos .md y nos va a
dejar manipularlos y acceder a su contenido, transformándolo en HTML para
manipularlo en el frontend 😁
Les propongo algo, armemos un primer post y después la página donde va a estar
el listado de nuestros posts (de cómo mostrar cada post nos encargamos después
😉), así que empecemos por crear en /posts un archivo nuevo con alguna fecha
anterior a hoy y un nombre, por ejemplo primer-post.md y llenémoslo con
texto, a mi me gusta mucho este lorem ipsum:
---
title: 'Mi primer post'
date: '2019-10-20'
---
# Mi primer post!
_Este_ es un post **BUENISIMO** y lleno de texto de
[este lorem ipsum](https://jeffsum.com/).
God help us, we're in the hands of engineers. Eventually, you do plan to have
dinosaurs on your dinosaur tour, right? Must go faster. Remind me to thank John
for a lovely weekend. You really think you can fly that thing? Remind me to
thank John for a lovely weekend.
Life finds a way. Life finds a way. You know what? It is beets. I've crashed
into a beet truck. Must go faster... go, go, go, go, go! Checkmate... This thing
comes fully loaded. AM/FM radio, reclining bucket seats, and... power windows.
Eventually, you do plan to have dinosaurs on your dinosaur tour, right?Todo muy lindo pero, qué son esos cosos --- ahí arriba? Eso se llama
frontmatter y todo lo que esté ahí adentro nos sirve como una especie de
metadata de cada post. En un ratito lo usamos y creo que se va a entender un
poco más. Por ahora volvamos a levantar nuestro servidor local de desarrollo
(gatsby develop) y vamos a ver si podemos consultar en GraphiQL el post
que acabamos de hacer 😁
Si prestamos atención, vemos que se agregaron dos campos nuevos para consultar
nuestro markdown, allMarkdownRemark y markdownRemark. Como viene siendo, el
que tiene all es para traer todos y el otro es para filtrar uno solo.
Nosotros queremos listar todos! Así que listemos toda la data que podamos que
nos parezca útil:

Recuerdan que edges representaba la colección y node cada uno de los objetos de la colección, no? Es bueno refrescarlo por las dudas 😊 Además, el plugin nos facilita un excerpt (extracto) como para mostrar en una preview, ahora lo vamos a usar!
FANTASTICO, ya tenemos el backend configurado, la consulta para traer
nuestros posts, qué nos faltaría? Una página donde listarlos, no? Creemos
entonces en /pages un archivo nuevo que se llame blog.js y completemos como
hicimos para mostrar nuestros repos. La estructura y la idea del archivo sería
muy parecida, así que les dejo cómo lo hice yo en estas pestañas así pueden
intentarlo ustedes 😁
import React from 'react'
import { Link, useStaticQuery, graphql } from 'gatsby'
import Layout from '../components/layout'
import SEO from '../components/seo'
const BlogPage = () => {
const posts = useStaticQuery(graphql`
query {
allMarkdownRemark {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "DD MMMM, YYYY", locale: "es")
}
excerpt
}
}
}
}
`)
return (
<Layout>
<SEO title="Blog" />
<h1>Hola</h1>
<p>Este es mi blog :)</p>
<p>Tengo en total {posts.allMarkdownRemark.totalCount} post(s)</p>
{posts.allMarkdownRemark.edges.map(({ node }) => (
<article
style={{
border: '1px solid grey',
borderRadius: '5px',
margin: '24px',
padding: '12px',
}}
>
<h2>{node.frontmatter.title}</h2>
<i>{node.frontmatter.date}</i>
<p>{node.excerpt}</p>
</article>
))}
</Layout>
)
}
export default BlogPageUna vez que lo tengamos todo listo, guardamos el archivo, esperamos un poquito a que se haga el rebuild en nuestro servidor local y vamos a nuestra página recién creada a ver qué tan lindo quedó todo! http://localhost:8000/blog
Listo ya tenemos nuestro blog, chau nos vemos (?) jaja nah, todavía falta ver cómo hacemos para ver cada post por separado 😜 Además, no vamos a tener un blog con un solo post, armemos otro! Esta vez usemos la fecha de hoy:
Nombre de ejemplo: segundo-post.md
---
title: 'Segundo post'
date: '2019-10-26'
---
Vengo a hacerle compañía al primer post, que cree que tiene data interesante
pero son palabras sin sentido, pobrecito 😢Guardamos y debería sumarse a la lista de nuestros posts automáticamente ✨ La
magia de Gatsby y GraphQL y todo esto 😍 Pero... El orden de esos posts me
parece que está MAL, yo quiero el más nuevo arriba de todo... Ah, sí, tal como
usamos filtros para traer nuestros repos antes, tenemos una forma de ordenar
los posts (se acuerdan del orderBy que usamos?), cambiemos en la query de
nuestro archivo blog.js donde dice solamente allMarkdownRemark por:
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC })Sí, entre frontmatter y date hay 3 (tres) guiones bajos, no me miren mal
Y ahora vamos a tener nuestros posts ordenados debidamente, con el más nuevo arriba de todo 😉 Hicimos que se ordene (sort) y le indicamos que use el campo (field) fecha del frontmatter (frontmatter___date) en orden descendiente (order: DESC).
Phew~ cansador esto, no? Pero falta lo mejor! (?) Mostrar los posts uno por uno,
que por lo que sabemos hasta ahora, para hacer páginas creamos un archivo en
/pages y ya está, aunque crear una página por cada post no suena muy
conveniente, no? 🤔 Necesitamos una forma de crear las páginas de manera
programática, y para eso tenemos APIs en nuestro backend 😎 Vamos a usar
onCreateNode y
createPages.
Empecemos por ver para qué usamos cada uno, así no nos mandamos y tenemos que volver para atrás:
- createPages nos sirve para crear páginas de forma programática, a las
cuales se les genera un contexto y nosotros por ese contexto podemos pasarle
data de cada post, con qué podríamos traer data de cada post, se les ocurre?
😉 Cuando tengamos eso, nos falta una forma de generar una URL para poder
acceder a cada
pagecreada, para eso usamos onCreateNode. - onCreateNode vamos a usarlo para que cuando se cree un Nodo, tome ciertos datos únicos de cada post y nos genere algo que se llama slug, que va a ser lo que defina la URL del post.
Creando las URLs
Empecemos por crear la URL para ya tenerla en el contexto cuando creemos cada
page, así que entremos a gatsby-node.js e implementemos las APIs, que se
implementan exportando funciones con el nombre de la API a utilizar:
exports.onCreateNode = ({ node }) => {
console.log('Tipo de nodo: ', node.internal.type)
}Ahora cortemos y volvamos a correr el servidor local, y miremos la consola, si prestamos atención se van a crear distintos tipos de nodos: SitePage, SitePlugin, Directory, File... Pero hay uno que nos interesa más que otros, dijimos que íbamos a trabajar con nuestros posts que son de tipo Markdown, si vemos en la consola lo más parecido es MarkdownRemark! Ya tenemos cómo filtrar nuestros posts entre los nodos creados durante el build, ahora a trabajarlos!
Para generar el slug con algo propio de cada post, podemos utilizar el nombre
de archivo, lo cual sería único de cada nodo. Pero para poder acceder a esto,
necesitamos movernos en el grafo de nodos (suena de una peli de ciencia
ficción 😂) e ir al nodo padre, que es File, el tipo de nodo que contiene la
data de nuestros archivos en disco, modifiquemos en nuestra función recién
creada:
exports.onCreateNode = ({ node, getNode }) => {
if (node.internal.type === `MarkdownRemark`) {
const fileNode = getNode(node.parent)
console.log(`\n`, fileNode.relativePath)
}
}Como estamos modificando los archivos gatsby-algo, por cada modificación que hagamos, si queremos ver los resultados, debemos frenar y volver a correr gatsby develop en la consola. Y vamos a ver los nombres de nuestros archivos flotando por ahí! YAY 🎉🥳
Sigamos, como la gente de Gatsby es buena y nos quiere, nos dice que la lógica
para crear estos slugs es complicada, así que el plugin
gatsby-source-filesystem ya viene con una función para ayudarnos para "crear
el path del archivo", ergo createFilePath:
//Acuerdense que la sintaxis acá es 'node-style'
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode }) => {
if (node.internal.type === `MarkdownRemark`) {
console.log(createFilePath({ node, getNode }))
}
}Y si reiniciamos el server, vemos los strings! Ya tenemos el dato necesario, lo
que tenemos que hacer ahora es sumarlo a la información del nodo, extender el
nodo, para que podamos consultar el slug en el frontend. Cuando implementamos
una API, recibe como parámetros una colección de actions (acciones) que
podemos usar para manipular state, en nuestro caso nos sirve cierta action
para extender nuestros nodos:
createNodeField.
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
// Conseguimos la action necesaria mediante destructuring
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode })
// Generamos un nuevo field que extiende nuestro nodo
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}Reiniciemos el servidor y vayamos a GraphiQL, deberíamos tener un valor nuevo
adentro de cada allMarkdownRemark -> edges -> node que se llame fields!
Prueben si trae los slugs que recién creamos y sigamos con la creación de las
páginas con createPages! 😁😁
Creando páginas de forma programática
Bueno, venimos bien, agregamos un valor a los nodos de Markdown (nuestros
posts), lo cual nos habilita a poder consultar ese nuevo valor y mandarlo al
front en cada página mediante createPages. Tengan a mano esto que está en
negrita para entender lo que estemos haciendo. El tema es que cada página creada
necesita un template (plantilla) donde mostrar la data que consigamos de
nuestros posts, así que empecemos por ahí, creemos una carpeta /templates
adentro de /src, y en /templates creemos un archivo blog-post.js con lo
siguiente:
import React from 'react'
import Layout from '../components/layout'
export default () => {
return (
<Layout>
<div>Acá va un post!</div>
</Layout>
)
}Ahora que tenemos una plantilla (que por ahora no hace nada mas que mostrar ese
texto siempre), sigamos con lo nuestro, nosotros sabemos que para usar una API
en nuestro backend, tenemos que exportar la función con el mismo nombre que la
API a usar, así que igual que antes, agregamos exports.createPages pero con
una salvedad: entre las cosas que nuestras APIs reciben por parámetro (de las
cuales antes usamos node, getNode, actions), vamos a usar graphql, la cual es
una función que retorna una
Promise,
las cuales se pueden resolver de dos formas, con .then() y .catch() o con
async/await. Vamos con la segunda opción ya que de otra forma tendríamos
algunos problemas de asincronismo con las funciones que nos provee Gatsby, así
que agreguemos esto:
//En gatsby-node.js, abajo de nuestra función exports.createNode:
exports.createPages = async ({ graphql, actions }) => {
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
console.log(JSON.stringify(result, null, 2))
}Y vamos a ver en la consola el resultado de nuestra query, son los slugs que generamos antes! 😄

Ya tenemos el template y los slugs, ahora unamos todo con una action de createPages que nos permite configurar y pasar data por medio de un context a cada página creada: createPage 😏
Vamos a mandarle en path, la dirección que va a tener cada post, o sea, el
slug; también vamos a mandarle en component una referencia al componente
template que creamos antes, y por context vamos a mandar el slug, que la
data enviada por context va a estar disponible en queries de página como
variables de GraphQL. Ah, y para referenciar al componente ahora que estamos
desde el backend, necesitamos importar path:
//En gatsby-node.js
//Arriba de todo agregamos esta línea
const path = require(`path`)
//Luego nuestro createPages debería quedar así
exports.createPages = async ({ graphql, actions }) => {
//Conseguimos nuestra action
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
//Configuramos cada página con la data de cada nodo
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
slug: node.fields.slug,
},
})
})
}Nuestro gatsby-node.js debería quedar algo así:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
// Conseguimos la action necesaria mediante destructuring
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode })
// Generamos un nuevo field que extiende nuestro nodo
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
//Conseguimos nuestra action
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
//Configuramos cada página con la data de cada nodo
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/blog-post.js`),
context: {
slug: node.fields.slug,
},
})
})
}Si reiniciamos el servidor local se van a crear nuestras páginas, VAMO! Pero, cómo accedemos? A mano? 🤔 Una forma de ver las páginas creadas es forzar un 404, así que vayamos a una dirección random http://localhost:8000/hn934b3 y vamos a verlas en el listado, háganle click y verán que carga el template que hicimos, que mucho como plantilla no estaría funcionando 🤣 Arreglemos eso!
Lo que podemos hacer ahora es consultar la data de cada post filtrando por su slug! Eso lo hacemos en nuestro template con una query de página:
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
export default ({ data }) => {
const post = data.markdownRemark
return (
<Layout>
<div>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</div>
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`Eh? Query de página??? Sí, en Gatsby hay dos formas de consultar data:
- Normal (o Page) Queries (Query de página)
- Static Queries (Query estática)
Una de las principales diferencias entre las dos es que la Query de página puede usarse solamente en los componentes de página, y era la única forma de consultar data en Gatsby v1. Luego desarrollaron la Query estática, que apareció en Gatsby v2 y fue fundamental para eliminar lo que se llama prop drilling, que es pasar data (o state) mediante props de padre a hijos en múltiples niveles, suponiendo que a veces esa data se pasaba unas 3 ó 4 (o más) veces, lo cual volvía el código inmantenible. Las Static Queries pueden utilizarse tanto en componentes comunes como en componentes de página.
Y la última gran diferencia es que la Query de página acepta variables,
las cuales se reciben mediante el contexto de página o pageContext, el cual
creamos recién en gatsby-node.js en la función createPage. La estática, tal
como su nombre indica, no acepta variables porque no tiene acceso al
contexto de página.
En la query de página de arriba vemos que se indica que esa query recibe una
variable (indicada con el signo $) de nombre slug y de tipo String, el
signo de exclamación indica que esa variable es requerida.
Bueno, sigamos con el blog! 😛
Ya casi no nos queda nada! Nos falta vincular cada post del listado con su
página creada! Vayamos a blog.js en pages y agreguemos lo que nos falta,
se los dejo a ustedes y la solución acá abajo, no pispeen sin intentarlo eh!!!
😝
import React from 'react'
import { Link, useStaticQuery, graphql } from 'gatsby'
import Layout from '../components/layout'
import SEO from '../components/seo'
const BlogPage = () => {
const posts = useStaticQuery(graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "DD MMMM, YYYY", locale: "es")
}
fields {
slug
}
excerpt
}
}
}
}
`)
return (
<Layout>
<SEO title="Blog" />
<h1>Hola</h1>
<p>Este es mi blog :)</p>
<p>Tengo en total {posts.allMarkdownRemark.totalCount} post(s)</p>
{posts.allMarkdownRemark.edges.map(({ node }) => (
<Link
to={node.fields.slug}
style={{ textDecoration: 'none', color: '#2f2f2f' }}
>
<article
style={{
border: '1px solid grey',
borderRadius: '5px',
margin: '24px',
padding: '12px',
}}
>
<h2>{node.frontmatter.title}</h2>
<i>{node.frontmatter.date}</i>
<p>{node.excerpt}</p>
</article>
</Link>
))}
</Layout>
)
}
export default BlogPageTerminado nuestro blog! Ya nos queda ponerlo lindo pero la funcionalidad está! 😀😀😀😀 Espero que hayan disfrutado este workshop, cualquier duda o sugerencia escríbanme y con gusto ayudo en lo que pueda, pueden encontrarme en Twitter como @AgustinDMulet, nos vemos gente!!!