Come ho costruito il mio blog personale

Se anche a voi è capitato di dover costruire un semplice sito da soli vi siete sicuramente scontrati con la vastità di strumenti e tecnologie innovative che al giorno d'oggi abbiamo a disposizione.

Nel corso degli anni ho provato ad utilizzare gran parte di questi strumenti, sia per scopo didattico che per la realizzazione di veri e propri prodotti finiti. Ogni volta però mi scontravo con alcuni problemi comuni:

  1. manutenzione del codice,
  2. difficoltà nella scrittura degli articoli,
  3. distribuzione dell'applicativo
  4. sforzo mentale per riprendere in mano il progetto anche dopo mesi di inattività.

In questo articolo proverò a dare una visione generale affidandomi all'esperienza che nel corso degli anni ho maturato per riuscire a mitigare i punti di frizione più comuni che uno sviluppatore si trova a dover affrontare quando deve creare da zero un sito semplice e di facile uso.

Cercherò di rispondere ad alcune delle questioni più comuni che ho incontrato in modo da fornire una sorta di roadmap da seguire per chi si vuole cimentare nella realizzazione del proprio sito/blog.

L'infrastruttura

Ho scelto di costruire il mio sito personale utilizzando NuxtJS come punto di partenza.

NuxtJS è un framework basato su Vue che permette uno sviluppo di applicativi semplice e potente.

L'ecosistema di NuxtJS offre una estensione particolare nuxt/content che rende la gestione dei contenuti mooolto semplificata.

Tutto il lavoro di routing delle pagine viene delegato al motore interno di NuxtJS e la gestione dei contenuti avviene grazie all'estensione nuxt/content.

Il deploy di questo sito è stato fatto all'interno di un mio server personale su AWS che uso per tutti i progetti di piccole dimensioni. L'istanza è una t3.medium ospitata nella zona eu-central-1 con sistema operativo Rocky Linux 8. Ho scelto di sfruttare la funzionalità SSR (static site generation) in modo da pre-generare tutte le pagine e gli assets prima di caricarli sul server.

Lo stile ed il layout che viene applicato proviene da Tailwind con alcune regole personalizzate. Sempre della famiglia di TailwindLab utilizzo alcuni componenti standardizzati ed accessibili che provengono da headlessui versione per Vue.

Gestione dei contenuti

La mia necessità principale è quella di escludere quanto più possibile il blocco di dover scrivere gli articoli o le pagine che ho in mente di scrivere.

Come necessità secondaria vorrei poter dare al lettore un modo per interagire con la pagina e provare con mano lo strumento o lo snippet di codice che sto analizzando.

Il segreto in questo caso è proprio l'estensione nuxt/content.

L'estensione nuxt/content usa Remark alla base e lavora su tutti questi tipi di files: .md, .yaml, .json, .csv, .xml; e li arricchisce con alcuni metadati importanti (dir, path, slug, extension, createdAt, updatedAt) per la successiva rielaborazione.

In aggiunta, elabora il tipo di file .md (Markdown) e lo estende all'uso di componenti Vue al suo interno.

Anche se non hai mai sentito parlare di Markdown lo hai sicuramente usato e visto all'opera.

Markdown è un formattatore di testo che fornisce una scrittura semplificata dei contenuti.

Per fare qualche esempio banale:

Questo è un paragrafo.

In questo paragrafo ci sono delle **parole in bold**.

Mentre qui sotto vediamo una lista:

- Primo elemento
- Secondo elemento
- ect..

Markdown necessita di uno step aggiuntivo che serve per "compilare" il risultato e trasformarlo in HTML in modo da poter essere correttamente visualizzato da un browser. Ogni paragrafo sarà trasformato in un <p>, le liste in <ul> e gli asterischi in <strong>.

Le regole di formattazione base di Markdown non sono molte ma fortunatamente ci sono delle estensioni che se ne occupano. nuxt/content ci regala la possibilità di arrichire ulteriormente in nostri contenuti con l'inserimento di componenti Vue.

import Component from "../components/Component";

Questo è un paragrafo introduttivo:

<Component title="" data=""></Component>

Ecco che abbiamo a disposizione uno strumento molto potente e versatile per generare contenuti arrichiti, interessanti e utili per il lettore. Permettere al lettore di interagire con la pagina trasforma il suo apprendimento da passivo ad attivo.

Praticamente tutto quello che puoi sviluppare con Vue può essere inserito e presentato all'interno di una pagina o articolo.

In questo paragrafo ho risposto ad alcune domande presenti all'inizio di questa analisi:

  1. Sono in grado di pubblicare contenuti semplicemente creando un nuovo file `.md` e iniziando subito a scrivere. Qualsiasi idea mi passi per la testa la posso scrivere direttamente su un nuovo file e non devo nemmeno strutturarlo con HTML perchè tanto sarà il compilatore a farlo per me.
  2. riesco a fornire ai miei lettori degli strumenti interattivi che permettono di apprendere e provare le logiche che voglio esaminare.
  3. ho la possibilità di estrarre metadati dai documenti che mi permettono di filtrare, ordinare, pubblicare in prima pagina o categorizzarli in base al tema trattato.

Non è l'unico modo per ottenere tutti questi vantaggi, basti pensare alla contro parte React con NextJS e MDX che permette di sfruttare le stesse identiche funzionalità basate su un framework diverso.

FrontMatter

Quando si parla di metadati FrontMatter è la strada più semplice da seguire. Ovviamente nuxt/content è già pre-confezionato per l'uso di FrontMatter.

E' possibile aggiungere un blocco in formato YAML all'inizio di ogni documento per definire una serie di metadati (chiave-valore) che vanno ad arricchire il contenuto.

Ad esempio per questo articolo il blocco FrontMatter è:

---
type: article
title: Come ho costruito questo sito
description: Una descrizione dettagliata di come ho costruito questo sito!
img: images/articles/
date: "2021-07-19T12:00:00"
status: draft
---

(tutto il resto del contenuto)

Vediamo nella pratica come posso utilizzare queste informazioni per generare una lista di articoli da mostrare nella pagina principale dedicata:

ArticleList.vue
export default {
    "name": "article-list",

    async fetch() {
        this.articles = await this.$content("articles")
            .without(["body"])
            .where({ "status": "published" })
            .sortBy("date", "desc")
            .fetch();
    },

    data() {
        return {
            "articles": [],
        }
    }
}

E' interessante vedere come nuxt/content ci mette a disposizione un unico punto di accesso this.$content("folder") che è in grado di utilizzare i metadati a suo favore per selezionare, escludere, ordinare.

  1. Carica tutti i files .md che sono presenti nella directory contents/articles.
  2. Carica il frontmatter di ogni file.
  3. Escludi il corpo del documento (per velocizzare la lettura).
  4. Filtra solo i documenti che sono stati pubblicati.
  5. Ordina in base alla data di pubblicazione.
  6. Assegna i dati ad una variabile e rendili disponibili nella pagina.

Una volta ottenuto l'array di articoli pubblicati li posso facilmente mostrare usando il componente Vue apposito:

<article-list-item v-for="(article, i) in articles" :key="i" :article="article"></article-list-item>

In homepage

Se provi a guardare in homepage potrai notare che c'è una lista che raggruppa sia gli articoli che le pagine nello stesso componente. Adesso ti spiego come realizzarlo.

Il problema è molto semplice: ho 2 cartelle da interrogare e voglio mostrare in ordine cronologico tutte le pagine e gli articoli che sono segnati come sticky sulla homepage.

Ometto alcune parti per chiarezza:

ContentList.vue
export default {
    "name": "content-list",

    async fetch() {
        this.articles = await this.$content("articles")
            .without(["body"])
            .where({ "status": "published", "list": "sticky" })
            .fetch();

        this.pages = await this.$content("pages")
            // same as articles
    },

    "computed": {
        contents() {
            return [...this.articles, ...this.pages].sort((a, b) => a.date < b.date);
        }
    }
}

Sono obbligato a caricare dalle due cartelle i documenti che voglio mostrare. Utilizzando una proprietà computed ottengo un unico array che posso riordinare in base alla data di pubblicazione.

Questo breve esempio mostra tutta la potenza che NuxtJS e nuxt/content ci mette a disposizione e quanto facile possa essere ottenere ottimi risultati.

Da utilizzare poi come:

ContentList.vue
<template v-for="(content, i) in contents">
    <article-list-item v-if="'article' == content.type" :article="content" :key="i"></article-list-item>
    <page-list-item v-if="'page' == content.type" :page="content" :key="i"></page-list-item>
</template>

Persistenza

I lettori più esperti si staranno sicuramente chiedendo come mai non ho ancora parlato di persistenza del dato e di database. Dove vengono salvate tutte queste informazioni?

E' molto semplice, in questo sistema non c'è bisogno di un database che mantenga i contenuti. Sono semplicemente salvati nel filesystem come dei comunissimi files.

Questo articolo nel filesystem si trova nel server al percorso content/articles/come-ho-costruito-il-mio-sito.md.

Per ora non ho la necessità di salvare informazioni persistenti nel tempo. Appena sentirò l'esigenza di una integrazione più specifica, proporrò un articolo dove analizzerò quali sistemi ci siano a disposizione e come avrò deciso di utilizzarli per questo sito.

Nel frattempo:

  • sto risparmiando il costo per la gestione del database (un costo di principale importanza per chi si occupa di questo mestiere).
  • ho evitato una complessità di non poco conto (risparmiando sulla costruzione di una api specifica per la persistenza del dato).

Snippet di codice

Quasi tutti i formattatori Markdown ci permettono di creare degli esempi di codice utilizzando 3 backticks (```).

L'estensione nuxt/content esegue questa formattazione in modo automatico utilizzando le classi di PrismJS e ci permette di espandere le funzionalità di base di Markdown.

Ad esempio possiamo pubblicare alcune linee di codice, in questo modo:

server.js
const http = require("http");

http.createServer((req, res) => {
}).listen(3000);

oppure segnalare le modifiche sullo snippet:

server.js
const http = require("http");
const bodyParser = require("body-parser");

http.createServer((req, res) => {
  bodyParser.parse(req, (error, body) => {
    res.end(body);
  });
}).listen(3000);

La forza del framework si esprime in tutta la sua potenza in queste poche righe. Basta un minimo di configurazione per ottenere un effetto professionale. Nel mio caso ho scelto di utilizzare un tema abbastanza conosciuto come il Dark+ di VSCode.

nuxt.config.js
    ...

    // Content module configuration: https://go.nuxtjs.dev/config-content
    "content": {
        "markdown": {
            "prism": {
                "theme": "prism-themes/themes/prism-vsc-dark-plus.css"
            }
        }
    },

    ...

Assets e stile

Devo ammettere che lo stile non è il mio forte. E così, non sono un grafico, non sono un esteta e non sono neanche tanto bravo a vestirmi in abbinato.

Ho capito molto presto che la mia attitudine non era quella di creare prodotti gradevoli e belli ma piuttosto creare qualcosa di funzionale e ben fatto. Sono un programmatore. Nella mia testa non c'è spazio per l'estetica.

Ho imparato alcuni trucchi per non sfigurare ogni volta:

  1. Quando ho a che fare con il reparto grafico cerco sempre di imparare le terminologie e i segreti (anche i più piccoli) per ottenere un risultato decente. Faccio tante domande per capire il senso delle cose.
  2. Cerco ispirazione dagli altri. Navigo il web ogni giorno e se trovo degli elementi grafici particolarmente interessanti li analizzo e cerco di farli miei.
  3. Faccio maturare le idee nel tempo e chiedo consiglio a chi ne sa più di me. Trovo molto utile tornare su un progetto dopo qualche giorno per acquisire una visione più distaccata che mi permette di vedere l'insieme sotto una nuova luce.

Si pensa spesso che per essere un programmatore completo sia necessario avere anche delle basi artistiche e gusto grafico ma non è così. Io credo che il bravo programmatore debba conoscere gli strumenti che ha a disposizione e li deve saper applicare quando sono necessari. Non deve essere un artista.

Con un po' di aiuto e qualche consiglio anche i peggiori possono creare prodotti gradevoli e se proprio non funziona, chiedete aiuto ad un professionista.

Nel mio piccolo, ho realizzato un componente che mi permette di gestire l'immagine laterale dei miei contenuti e di aggiungere un tocco accattivante quando il sito viene visualizzato da desktop.

E' solo una questione di stile:

AsideSection.vue
export default {
    "name": "aside-section",

    "props": {
        "image_path": {
            "type": String,
            "required": true,
        }
    },

    "computed": {
        style() {
            return {
                "background-image": `url('${this.image_path}')`,
                "background-position": "100%",
                "background-size": "cover",
            }
        }
    }
}

Conclusione

Mi auguro che questa panoramica generale possa essere di ispirazione per i nuovi sviluppatori web-oriented dato che ho esaminato una tecnologia abbastanza moderna.

Se credi che debba approfondire alcuni aspetti di questo sistema vieni a trovarmi su Twitter o su Linkedin sarò felice di discutere con te.