Freddy Montes

Soy un Frontend Developer y diseñador. Aprendo y enseño todo sobre Tecnologías y Diseño Web.

Costa Rica (GMT-6)

🇺🇸 Translate
InstagramTwitterGithubLinkedInStackOverflow

Construyendo un sitio web con Gatsby y un headless CMS - Parte 1

Freddy Montes|

Esta es una serie de dos entradas de blog, [aquí está la segunda parte].(/blog/building-a-website-with-gatsby-and-a-headless-cms-part-2).

Si está buscando lanzar un sitio web, micrositio o lading page pequeño, estático y rápido, debes considerar un generador de sitios estáticos como Gatsby.

Este artículo lo guiará a través del proceso de uso de Gatsby junto con dotCMS, un CMS sin cabeza de código abierto basado en Java, para construir un sitio web estático que usa llamadas a API para extraer contenido creado, almacenado y administrado en dotCMS, y presentado por Gatsby.

¿Que necesitas?:

  1. Node.js and NPM
  2. Conocimiento React.js
  3. GraphQL conocimiento (no es necesario, pero lo va a utilizar. Puedes copiar + pegar)

¿Qué es Gatsby?

Gatsby se define a sí mismo como "un generador de sitios moderno y ultrarrápido para React" y sí, los sitios generados son rápidos y listos para usar.

El rico ecosistema de plugins de Gatsby le permite crear sitios con datos de una o varias fuentes:

Extraer datos de headless CMSs (como dotCMS), servicios SaaS, APIs (como el API de contenido de dotCMS) ay le permite traer los datos a su página usando GraphQL.

Beneficios del generador de sitio estático

Seguridad

No hay bases de datos y la amenaza de inyección de código es casi nula.

Fiabilidad

Puede servir archivos HTML en todas partes.

Velocidad

Los sitios estáticos regulares son rápidos (de nuevo, sin base de datos ni backend) y con Gatsby + React son incluso más rápidos.

Escalabilidad

Servir sitios web estáticos con archivos HTML se puede escalar fácilmente aumentando el ancho de banda.

Limitaciones del generador de sitio estático

La obtención de datos para un sitio de Gatsby ocurre en el momento de la compilación, lo que significa que si se crea contenido nuevo en dotCMS, no estará disponible en su sitio hasta la próxima compilación, o la próxima vez que "ejecute" su CMS generador de sitios estáticos que es Gatsby en el caso de hoy.

Pero ... también hay soluciones para esto

  1. Puede crear un cronjob que se ejecute cada n minutos para construir y desplegar su sitio.
  2. ¡Páginas de aplicaciones híbridas al rescate! Recuerde que Gatsby tiene React.js en su núcleo, lo que significa que puede realizar solicitudes de red a las API y obtener nuevos datos en sus componentes.

Cómo crear un sitio Gatsby

Instalar Gatsby

Gatsby tiene esta increíble herramienta CLI para crear, administrar y ejecutar proyectos de Gatsby, para instalarlo, vaya a su terminal y ejecute:

$ npm install --global gatsby-cli

Si todo salió bien, puede escribir:

$ gatsby -v

Y obtén 2.4.3 o una versión similar:

Gatsby install

Crear un sitio de Gatsby

Ahora usemos la herramienta CLI de Gatsby para crear un sitio:

$ gatsby new dotcms-site

Nota: Puede reemplazar "dotcms-site" con el nombre que desee para su proyecto.

El comando que acaba de ejecutar:

  1. Creó un nuevo sitio con el iniciador predeterminado de Gatsby.
  2. Creó una carpeta con el nombre del proyecto (dotcms-site).
  3. Instaló todos los paquetes npm que necesitan para ejecutar el sitio.
  4. ¡Hizo su vida más fácil!

Ejecutemos nuestro nuevo sitio de Gatsby:

$ cd dotcms-site

Y dentro de la carpeta corremos:

$ gatsby develop

Este comando inicia un servidor de desarrollo. Podrá ver e interactuar con su nuevo sitio en un entorno de desarrollo. También tiene recarga en vivo, por lo que cualquier cambio que realice en sus archivos podrá verlo inmediatamente en su sitio.

Ahora abra un navegador y vaya a http: // localhost: 8000 y si todo salió bien, debería algo como:

Gatsby Hello World

Para obtener datos, necesitas un plugin de origen

Gatsby tiene un sistema de plugins. Para obtener datos, necesitas lo que ellos llaman un "Source Plugin". Los plugins de origen "originan" datos desde ubicaciones remotas o locales en lo que Gatsby llama nodes.

Piensa en un nodo como el equivalente exacto de un contentlet en dotCMS. Así que si estás mostrando productos, cada objeto de producto que sacas de tu CMS sin cabeza es un nodo Gatsby.

Vas a escribir un plugin de fuente Gatsby que obtiene todos los contentlets en una instancia de dotCMS y los convierte en nodos Gatsby que luego puedes mostrar en nuestras páginas mediante consultas con GraphQL.

Traducción realizada con la versión gratuita del traductor www.DeepL.com/Translator

dotCMS to Gatsby Page Diagram

Antes de empezar, si quieres leer más sobre el plugin Gatsby Source su documentación y tutorial son realmente buenos.

Crear el plugin gatsby-source-dotcms

Lo esencial de un plugin es: un directorio con el nombre de tu plugin, que contiene un archivo package.json y un archivo gatsby-node.js:

Traducción realizada con la versión gratuita del traductor www.DeepL.com/Translator

|-- plugins
|-- gatsby-source-dotcms
    |-- gatsby-node.js
        |-- package.json

Comience por crear el directorio y cambie a él:

$ mkdir plugins
$ mkdir plugins/gatsby-source-dotcms
$ cd plugins/gatsby-source-dotcms

Crear un archivo package.json

Ahora crea un archivo package.json. Este describe tu plugin y cualquier código de terceros del que pueda depender. npm tiene un comando para crear este archivo por ti. Ejecuta

$ npm init --yes

para crear el archivo utilizando las opciones por defecto.

NOTA: Puedes omitir --yes si quieres especificar las opciones tú mismo.

Con la configuración hecha, pasa a añadir la funcionalidad del plugin.

Crear un archivo gatsby-node.js

Crea un nuevo archivo llamado gatsby-node.js en tu directorio gatsby-source-dotcms. Abre el archivo en tu editor de código favorito y añade lo siguiente:

exports.sourceNodes = ({ actions, createNodeId, createContentDigest }, configOptions) => {
    const { createNode } = actions
    // Gatsby añade una configOption que no es necesaria para este plugin, elimínala
    delete configOptions.plugins

    // el código del plugin va aquí...
    console.log('Testing my DotCMS plugin', configOptions)
}

¿Qué has hecho al añadir este código?

Has implementado la API sourceNodes de Gatsby, que Gatsby ejecutará como parte de su proceso de arranque. Cuando Gatsby llame a sourceNodes, pasará algunas funciones de ayuda (actions, createNodeId, y createContentDigest) junto con cualquier opción de configuración que se proporcione en el archivo gatsby-config.js de tu proyecto:

exports.sourceNodes = ({ actions, createNodeId, createContentDigest }, configOptions) => {...}

You do some initial setup:

const { createNode } = actions

// Gatsby añade una configOption que no es necesaria para este plugin, elimínala
delete configOptions.plugins

Y, por último, añadir un mensaje de marcador de posición:

console.log('Testing my DotCMS plugin', configOptions)

Cómo añadir el plugin dotCMS a tu sitio Gatsby

El esqueleto de tu plugin está en su lugar, lo que significa que puedes añadirlo a tu proyecto y comprobar tu progreso hasta ahora.

Abre gatsby-config.js desde el directorio raíz de tu sitio tutorial, y añade el plugin gatsby-source-dotcms:

module.exports = {
    siteMetadata: {
        title: 'Gatsby Default Starter',
    },

    plugins: [
        {
            resolve: 'gatsby-source-dotcms',
            options: {},
        },
    ],
}

Abra una nueva terminal en el directorio raíz de su sitio tutorial, luego inicie el modo de desarrollo de Gatsby:

$ gatsby develop

Comprueba las líneas después del éxito en PreBootstrap; deberías ver tu mensaje "Probando mi plugin" junto con un objeto vacío de las opciones de tu archivo gatsby-config.js:

Gatsby and dotCMS Plugin Node

Observe que Gatsby le está advirtiendo que su plugin no está generando ningún nodo Gatsby. Es hora de arreglar eso.

Obteniendo los datos de DotCMS

Como mencioné antes, necesitas obtener TODOS los contentlets de la instancia de dotCMS y convertirlos en nodos Gatsby.

El plan de acción

Para crear un nodo GraphQL que puedas consultar por tipo de contenido, necesitas dos cosas de dotCMS:

  1. El contentlet.
  2. El tipo de contenido de cada contentlet.

En dotCMS, necesitas usar el nuevo content types REST endpoint para obtener todas las variables de tipos de contenido y luego usar el endpoint de la API de contenido para obtener todos los contentlets para cada tipo de contenido en la instancia.

Combinarás los resultados de las dos peticiones para crear una gran colección de contentlets con una propiedad extra de contentType.

Añadir dependencias

Desde su carpeta de plugins (/plugins/gatsby-source-dotcms) ejecute:

$ npm install node-fetch --save

Abre tu archivo package.json y verás que node-fetch ha sido añadido a una sección de dependencias al final:

"dependencies": {
    "node-fetch": "^2.2.0"
}

node-fetch es un módulo ligero que trae window.fetch a Node.js para que puedas usar fetch para hacer la petición a los endpoints de dotCMS.

Crear una biblioteca JavaScript dotCMS

Para crear la librería dotCMS para obtener todos los contentlets y tipos de contenido que vayas a utilizar:

  1. Clases ES6
  2. Promise
  3. Async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)/[Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)

Asumo que estás familiarizado con estas tecnologías, así que no voy a explicar cómo funcionan en este tutorial con demasiado detalle.

Para empezar, crea un archivo dotcms-api.js dentro de la carpeta /plugins/gatsby-source-dotcms, abre el archivo y añade el siguiente código:

const fetch = require('node-fetch')

class DotCMSApi {
    constructor(options) {
        this.options = options
    }

    getBaseUrl() {
        return `${this.options.host.protocol}://${this.options.host.url}`
    }

    getContentletsByContentType(contentType) {
        const getUrl = () => {
            return `${this.getBaseUrl()}/api/content/render/false/query/+contentType:${contentType}%20+(conhost:${this.options.host.identifier}%20conhost:SYSTEM_HOST)%20+languageId:1%20+deleted:false%20+working:true/orderby/modDate%20desc`
        }

        return fetch(getUrl())
            .then((data) => data.json())
            .then((data) => data.contentlets)
            .then((contentlets) => {
                contentlets.forEach((contentlet) => {
                    contentlet.contentType = contentType
                })
                return contentlets
            })
    }

    async getContentTypesVariables() {
        const getUrl = () => {
            return `${this.getBaseUrl()}/api/v1/contenttype?per_page=100`
        }

        return fetch(getUrl(), {
            headers: {
                DOTAUTH: Buffer.from(
                    `${this.options.credentials.email}:${this.options.credentials.password}`
                ).toString('base64'),
            },
        })
            .then((data) => data.json())
            .then((contentTypes) => contentTypes.entity.map((contentType) => contentType.variable))
    }

    async getData() {
        const contentlets = await this.getContentTypesVariables().then((variables) => {
            return variables.map(async (variable) => {
                const data = await this.getContentletsByContentType(variable)
                return data
            })
        })
        return Promise.all(contentlets)
    }
}

exports.getContentlets = async (configOptions) => {
    const dotCMSApi = new DotCMSApi(configOptions)

    return dotCMSApi.getData().then((contentTypesContentlets) => {
        // Flatten nested array
        return [].concat.apply([], contentTypesContentlets)
    })
}

¿Qué hace todo este código?

const fetch = require('node-fetch')

Has importado el módulo npm, node-fetch, y se utilizará para hacer las peticiones a la instancia de dotCMS y obtener los datos que necesitas.

class DotCMSLibrary {
    constructor(options) {
        this.options = options
    }
}

Luego has creado una clase que contiene los métodos que utilizas para obtener los datos que necesitas (contentlets y tipos de contenido). Cuando creas una instancia de esta clase le pasas las opciones que añadirás al archivo gatsby-config.js.

getBaseUrl() {
    return `${this.options.host.protocol}://${this.options.host.url}`
}

Estos métodos simples se utilizan para obtener la URL de la instancia de dotCMS.

getContentletsByContentType(contentType) {
    const getUrl = () => {
        return `${this.getBaseUrl()}/api/content/render/false/query/+contentType:${contentType}%20+(conhost:${this.options.host.identifier}%20conhost:SYSTEM_HOST)%20+languageId:1%20+deleted:false%20+working:true/orderby/modDate%20desc`;
    };

    return fetch(getUrl())
        .then((data) => data.json())
        .then((data) => data.contentlets)
        .then((contentlets) => {
            contentlets.forEach((contentlet) => {
                contentlet.contentType = contentType;
            });
            return contentlets;
        });
}

En este método, se hace la petición para obtener todos los contentlets del tipo de contenido específico que se pasará como parámetro, y a cada contentlet se le añade la propiedad contentType que se utilizará para consultar los datos. Este método se llama varias veces, una por cada tipo de contenido en la instancia de dotCMS.

async getContentTypesVariables() {
    const getUrl = () => {
        return `${this.getBaseUrl()}/api/v1/contenttype?per_page=100`;
    };

    return fetch(getUrl(), {
        headers: {
            DOTAUTH: Buffer.from(
                `${this.options.credentials.email}:${this.options.credentials.password}`
            ).toString('base64')
        }
    })
        .then((data) => data.json())
        .then((contentTypes) => contentTypes.entity.map((contentType) => contentType.variable));
}

Este método obtendrá una matriz de variables de tipo de contenido utilizando la nueva API de tipos de contenido y luego asignará el resultado a una matriz de variables.

NOTA: asegúrese de pasar el parámetro de consulta per_page=100 en la URL con la cantidad completa (o más) de tipos de contenido en su instancia.

async getData() {
    const contentlets = await this.getContentTypesVariables().then((variables) => {
        return variables.map(async (variable) => {
            const data = await this.getContentletsByContentType(variable);
            return data;
        });
    });
    return Promise.all(contentlets);
}

Y finalmente has utilizado getContentTypesVariables y getContentletsByContentType para obtener todos los contentlets de la instancia de dotCMS. Primero, obtienes un array de variables de tipos de contenido y para cada una de ellas haces una petición a la API de contenido de dotCMS para obtener todos los contentlets para cada tipo de contenido. Recuerda que este código se ejecuta en tiempo de compilación, lo que significa que no va a ser un problema de rendimiento hacer tantas peticiones.

La constante contentlets da como resultado un array de promesas de contentlets por lo que hay que utilizar Promise.all para devolver una única promesa que se resuelva cuando todas las promesas de contentlets se hayan resuelto.

exports.getContentlets = async (configOptions) => {
    const dotCMSApi = new DotCMSApi(configOptions)

    return dotCMSApi.getData().then((contentTypesContentlets) => {
        // Flatten nested array
        return [].concat.apply([], contentTypesContentlets)
    })
}

Finalmente, se exporta un método de esta librería, donde se inicializa la API de DotCMS con las opciones de configuración del archivo gatsby-config.js se obtienen todos los contentlets de la instancia de dotCMS y se aplana en una gran colección de contentlets con una propiedad extra de tipo de contenido. El resultado será como:

[
    {
        "contentType": "newsItem",
        "owner": "dotcms.org.1",
        "identifier": "f60ed48b-1f5f-4a7b-b4b0-f5a857b41e6a",
        "inode": "734944ff-6f02-4337-b9fe-aef3c372dad8",
        "title": "This is a new item",
        "expire": "2020-01-02 02:19:00.0",
        "tags": "oil,investment,gas,prices,retiree:persona"
    },
    {...}
]

La propiedad contentType añadida en esta colección de contentlets es clave, porque la usaremos para hacer nuestras consultas GraphQL.


Gracias por leer esta entrada del blog. Cualquier comentario o pregunta me puedes preguntarme en mi Twitter: @fmontes