Construir un blog en FireCMS Cloud

No es necesario explicar los beneficios de usar un CMS headless en lugar de un enfoque de blog tradicional como WordPress, pero aquí hay algunos:
- es más fácil y rápido de desarrollar, ya que no hay acoplamiento entre frontend y backend.
- dado que tu frontend es independiente, puedes cambiarlo de cualquier forma que desees, lo mismo ocurre con el backend.
- está diseñado para aplicaciones multicanal, puedes usar el mismo backend y CMS con múltiples apps y sitios web
- equipos más pequeños y especializados
- escalabilidad
- menores costes
- flexibilidad y simplicidad
Construyamos un blog con FireCMS
Sección titulada «Construyamos un blog con FireCMS»Vamos a construir una colección que aloje entradas de blog. Cada una de las entradas incluirá un array dinámico de elementos.
FireCMS tiene una funcionalidad integrada que te permite construir arrays dinámicos. La
propiedad de tipo array puede configurarse con la prop oneOf, para contener objetos
que almacenan un tipo específico de valor. ¡Esto es perfecto para construir la
estructura de datos de nuestras entradas de blog!
Estos son los tipos que usaremos:
- Definimos el tipo de la colección de blogs:
type BlogEntry = { name: string, header_image: string, created_on: Date, status: string, content: (BlogEntryImages | BlogEntryText | BlogEntryProducts)[];}- Y cada uno de los tipos del array
content:
type BlogEntryImages = { type: "images"; value: string[];}
type BlogEntryText = { type: "text"; value: string;}
type BlogEntryProducts = { type: "products"; value: object[]; // Usamos un objeto genérico aquí, siéntete libre de definir un tipo para tus productos}Crear la colección
Sección titulada «Crear la colección»Empecemos inicializando nuestra colección, sin ninguna propiedad:
export const blogCollection = buildCollection<BlogEntry>({ name: "Entrada de blog", path: "blog", properties: {}});Propiedades básicas
Sección titulada «Propiedades básicas»Luego vamos a añadir algunas propiedades simples para nuestras entradas.
- Queremos tener un título, que debe estar siempre establecido, así que ponemos
la validación
requireda true:
buildProperty({ name: "Título", validation: { required: true }, dataType: "string"})- Una imagen que estará en la parte superior de la entrada del blog:
buildProperty({ name: "Imagen de cabecera", dataType: "string", storage: { storagePath: "images", acceptedFiles: ["image/*"], metadata: { cacheControl: "max-age=1000000" } }})- y una fecha “created on” que se genera automáticamente cuando se crea el documento.
buildProperty( { name: "Creado el", dataType: "date", autoValue: "on_create"})Campo de estado condicional
Sección titulada «Campo de estado condicional»Ahora queremos añadir una propiedad string status que tendrá dos valores posibles:
published y draft. Solo queremos permitir el estado published
cuando el resto de los campos son correctos.
En este caso lo mantendremos simple, y solo comprobaremos si la imagen de cabecera está establecida:
buildProperty(({ values }) => ({ name: "Estado", validation: { required: true }, dataType: "string", columnWidth: 140, enumValues: { published: { id: "published", label: "Publicado", disabled: !values.header_image, }, draft: "Borrador" }}))Contenido de la entrada del blog
Sección titulada «Contenido de la entrada del blog»El contenido de nuestras entradas de blog necesita ser dinámico, para que los gestores de contenido puedan crear entradas complejas con diferentes componentes.
El contenido será un array de objetos, que tendrán un atributo type (que
funcionará como discriminador), y un atributo value.
Definiremos 3 tipos:
images: un array de imágenestext: un campo de texto Markdownproducts: un array de referencias a otra colección,productsen este caso
Usamos la prop oneOf en las propiedades de array, que está diseñada exactamente para este
caso de uso. Solo necesitas definir:
buildProperty({ name: "Contenido", description: "Ejemplo de array complejo con múltiples propiedades como hijos", validation: { required: true }, dataType: "array", columnWidth: 400, oneOf: { typeField: "type", // puedes omitir estas props `typeField` y `valueField` para usar los valores predeterminados valueField: "value", properties: { images: buildProperty({ name: "Imágenes", dataType: "array", of: buildProperty({ dataType: "string", storage: { storagePath: "images", acceptedFiles: ["image/*"], metadata: { cacheControl: "max-age=1000000" } } }), description: "Este campo permite subir múltiples imágenes a la vez y reordenarlas" }), text: buildProperty({ dataType: "string", name: "Texto", markdown: true }), products: buildProperty({ name: "Productos", dataType: "array", of: { dataType: "reference", path: "products" // necesitas definir una colección válida en esta ruta } }) } }})Crear una vista previa
Sección titulada «Crear una vista previa»¡Aprovechemos otra funcionalidad de FireCMS: vistas personalizadas para entidades!
FireCMS te permite añadir vistas adicionales a tus vistas de entidades, que están definidas como componentes React. Las props que recibes para construir este componente son la colección de entidades, la entidad original y los valores modificados.
En este caso, crearemos algunos componentes React para representar nuestra entrada
de blog tal como lo haría la aplicación frontend. Este es el mismo código que podrías
usar en cualquier framework SSR que use React, como next.js.
También podrías tener una configuración más compleja que envíe tus datos a tu app SSR a través de una API y renderice el resultado.
Puedes encontrar el código de BlogEntryPreview en
BlogEntryPreview.
Cuando hayas creado tu componente, hay 2 formas de añadirlo:
Regístralo en el CMS
Sección titulada «Regístralo en el CMS»La forma preferida es registrarlo en el CMS, para que esté disponible en el selector de vistas de la colección.
Para hacerlo, necesitas añadirlo a la prop entityViews de la configuración principal de la app FireCMS:
import { FireCMSAppConfig } from "@firecms/cloud";const appConfig: FireCMSAppConfig = { version: "1", collections: [], entityViews: [ { key: "blog_preview", name: "Vista previa", Builder: BlogEntryPreview } ],}Regístralo en la colección
Sección titulada «Regístralo en la colección»Si estás definiendo tu colección en código, también puedes registrarlo directamente en la colección:
import {buildCollection} from "@firecms/core";export const blogCollection = buildCollection<BlogEntry>({ name: "Entrada de blog", path: "blog", entityViews: [ { key: "blog_preview", name: "Vista previa", Builder: BlogEntryPreview } ], properties: { // ... }});Código completo:
Sección titulada «Código completo:»Si juntamos todas las partes que hemos construido en este tutorial, obtenemos el siguiente código para la colección de blog:
import { buildCollection, buildProperty } from "@firecms/core";import { BlogEntryPreview } from "./BlogEntryPreview";import { BlogEntry } from "./types";
export const blogCollection = buildCollection<BlogEntry>({ name: "Blog entry", id: "blog", path: "blog", entityViews: [{ key: "preview", name: "Preview", Builder: BlogEntryPreview }], properties: { name: buildProperty({ name: "Name", validation: { required: true }, dataType: "string" }), header_image: buildProperty({ name: "Header image", dataType: "string", storage: { mediaType: "image", storagePath: "images", acceptedFiles: ["image/*"], metadata: { cacheControl: "max-age=1000000" } } }), content: buildProperty({ name: "Content", description: "Example of a complex array with multiple properties as children", validation: { required: true }, dataType: "array", columnWidth: 400, oneOf: { typeField: "type", // you can ommit these `typeField` and `valueField` props to use the defaults valueField: "value", properties: { images: buildProperty({ name: "Images", dataType: "array", of: buildProperty({ dataType: "string", storage: { mediaType: "image", storagePath: "images", acceptedFiles: ["image/*"], metadata: { cacheControl: "max-age=1000000" } } }), description: "This fields allows uploading multiple images at once and reordering" }), text: buildProperty({ dataType: "string", name: "Text", markdown: true }), products: buildProperty({ name: "Products", dataType: "array", of: { dataType: "reference", path: "products" // you need to define a valid collection in this path } }) } } }), status: buildProperty(({ values }) => ({ name: "Status", validation: { required: true }, dataType: "string", columnWidth: 140, enumValues: { published: { id: "published", label: "Published", disabled: !values.header_image }, draft: "Draft" }, defaultValue: "draft" })), created_on: buildProperty({ name: "Created on", dataType: "date", autoValue: "on_create" }) }})