[ kalizi.dev ]

Gatsby.js, GitHub e Cloudflare: costruisci un sito statico con React

Durante il maggio del 2020 mi fu chiesto di rivedere e inviare il mio CV a un collega per una presentazione ufficiale di un progetto, così sono andato dritto al mio account Google Drive, dove tengo il CV, e ho iniziato una revisione per sistemare le date dei progetti e aggiungere le cose che avevo fatto dall'ultima revisione. La maggior parte dei miei progetti era coperta da NDA perché privati, avevo solo poche cose online e visibili a chiunque volesse rivedere il mio lavoro. A un certo punto, il mio occhio è caduto sulla mia email personale. Non scriverò qui la mia vecchia email (non mi piace lo spam), ma il suo formato era email(at)libero.it, quella terminazione del dominio mi ha lasciato di stucco e ho pensato "accidenti, perché ho ancora questa email e non ho un dominio personalizzato?".

Così sono andato su fill_this_with_your_fav_provider (non voglio fare pubblicità a un provider piuttosto che a un altro) e ho comprato un dominio, ho configurato un'email personalizzata e ho pensato "e se costruissi un sito web?".

La tecnologia

Un vantaggio dello sviluppare per te stesso è che non devi giustificare le tue scelte a nessuno: vuoi avvicinarti a quella nuova tecnologia che in realtà è usata solo da dieci persone al mondo? Puoi farlo. Vuoi restare su quello stack tecnologico con cui ti trovi a tuo agio? Nessun problema. Basta essere coerenti con le proprie scelte. Nel mio caso volevo velocità, il mio sito doveva essere incredibilmente veloce.

Programmer speed paranoid meme: from reddit

Programmers speed paranoid, from Reddit

Perché veloce? La cosa che ho sentito di più negli ultimi anni era "le connessioni di rete stanno diventando più veloci ovunque, quindi non devi preoccuparti delle risorse web che carichi nel tuo sito" e il mio pensiero su questa frase è che tutti pensano che sia SBAGLIATA. Se costruisci un sito buttandoci dentro roba come framework e librerie, aumenti le cose che verranno caricate dall'utente, e ogni file caricato dall'utente può essere visto come tempo e risorse gestite da un server o più server che rallenteranno il rendering o il caricamento della pagina per l'utente finale. Questo non significa che non devi usare framework o librerie, ognuno deve pensare a quale sia il perfetto compromesso tra funzionalità e tempo di caricamento.

Nel mio caso, volevo semplicemente che il mio sito fosse fatto di poche cose da caricare per avere un tempo di rendering molto breve e qual è il modo più veloce di servire un sito? HTML puro con poco CSS e JS. Ma questo mi ha fatto storcere il naso. Sono un fan del provare cose nuove che non conosco, e adoro React, quindi cercando su Google ho trovato Gatsby.

Gatsby è un framework web che aiuta a costruire siti statici veloci con React. Puoi costruire qualsiasi tipo di sito abbinando Gatsby alle API. Ma il focus principale è: essere veloce.

Gatsby loads only critical parts of the page, so your site loads as fast as possible. Once loaded, Gatsby prefetches resources for other pages so that clicking on the site feels incredibly fast. Gatsby lets teams focus on creating content-driven websites instead of worrying about performance.

Quando ho letto questo sul sito di Gatsby ho fatto 😍, così ho deciso di scegliere Gatsby e imparare una nuova tecnologia. Non sei ancora convinto di Gatsby? La documentazione di Gatsby mi copre le spalle con un paragrafo chiamato "Convincing Others", puoi leggerlo!

La configurazione

Gatsby ha un'ottima documentazione e un ottimo supporto per gli sviluppatori. Ho iniziato a leggere, ho imparato le basi ma volevo essere produttivo il più velocemente possibile, quindi qual è il modo migliore per iniziare? Semplicemente... iniziare!

npm install -g gatsby-cli

Ora gatsby è disponibile tra i tuoi comandi, avvia il tuo nuovo progetto.

gatsby new your-project-name https://github.com/gatsbyjs/gatsby-starter-hello-world

Questo clonerà gatsby-starter-hello-world nella cartella del tuo progetto. Questo è solo uno starter di base, se vuoi passare meno tempo a progettare l'architettura del tuo codice, puoi usare uno Starter di Gatsby dalla pagina degli starter.

Ora entra nella tua cartella e avvia il tuo progetto:

gatsby develop

Questo avvierà un server di sviluppo su localhost:8000 dove vedrai i progressi del tuo sito durante lo sviluppo. La configurazione è pronta, ora parliamo un po' di funzionalità e struttura del codice.

Le funzionalità del progetto: i plugin di Gatsby

Photo by Joel Rohland on Unsplash

Gatsby aiuta il tuo sviluppo con tanti plugin disponibili out-of-the-box che puoi integrare con poca configurazione. Parliamo di alcuni plugin disponibili che userò più avanti:

  1. Gatsby Source Filesystem: questo ti aiuterà a leggere dati (immagini, file JSON, CSV o altri dati) dal filesystem e a usarli per costruire il tuo sito.
  2. Gatsby SASS: scrivi SCSS/SASS, Gatsby produrrà CSS.
  3. Gatsby Google Fonts: non restare sui font predefiniti del browser, dai un'occhiata a Google Fonts, scegli qualche font che ritieni bello e usalo gratuitamente nel tuo sito. (Consiglio bonus: non sei un tipografo come me? Dai un'occhiata a FontPair).
  4. Gatsby Image: questo ottimizzerà il caricamento delle immagini. Ho già parlato di velocità? (Questo ha bisogno di altri due plugin per lo sharpening delle immagini)
  5. Gatsby React Helmet: questo aiuterà a modificare ciò che di solito non è modificabile con React, il contenuto del tag header (title, metatag, ecc...).
  6. Gatsby Sitemap: genera una sitemap per i motori di ricerca.
  7. Gatsby Google Analytics: non credo che questo abbia davvero bisogno di una descrizione.
  8. Gatsby i18n: multilingua reso facile. (Mentre scrivo questo, la pagina di gatsby su i18n non funziona bene, ma puoi trovare i18n su npm)
  9. Gatsby Smoothscroll: perché lo scrolling fluido rende tutto più elegante.

Quindi, con tutti questi plugin pronti a entrare in gioco, stressiamo un po' npm:

npm i gatsby-plugin-google-analytics gatsby-plugin-sitemap gatsby-plugin-react-helmet react-helmet gatsby-image gatsby-transformer-sharp gatsby-plugin-sharp gatsby-plugin-google-fonts node-sass gatsby-plugin-sass gatsby-source-filesystem gatsby-plugin-i18n gatsby-plugin-smoothscroll --save

Approfondiremo la configurazione dei plugin mentre costruiamo il sito.

Le basi: da Hello World a Hello Gatsby

Ora abbiamo una cartella src dove possiamo mettere le nostre cose. Una grande funzionalità di gatsby è che se metti un file in src/pages avrai online la pagina relativa: se crei src/pages/contact.js avrai online la route /contact!

Quindi inizia a strutturare il progetto creando queste directory:

  1. components: dove metteremo i nostri React Component riutilizzabili (es: pulsanti, icone).
  2. views: dove metteremo i componenti di visualizzazione delle pagine (es: nav, barra di ricerca con suggerimenti).
  3. styles: dove metteremo gli stili delle pagine in SCSS (o SASS se preferisci).

Per lo styling, ho deciso di scegliere la versione SCSS di Bootstrap npm i bootstrap-scss --save. Per compilare SCSS, devi aggiungere il plugin SASS alla configurazione inserendolo nell'array dei plugin in gatsby-config.js:

'gatsby-plugin-sass',

Questo compilerà automaticamente sass quando importi un file scss nei tuoi file JS. Quindi puoi iniziare a costruire il tuo SCSS.

Una delle prime cose da fare mentre si imposta un foglio di stile è scegliere i font, i font che volevo sono Montserrat (Sans-Serif, Body) e Inconsolata (Monospace, Title) da Google Fonts. Per la velocità non puoi semplicemente importare un font, devi modificare la configurazione del Gatsby Prefetch Google Fonts (dentro l'array dei plugin in gatsby-config.js):

{
  resolve: 'gatsby-plugin-google-fonts',
  options: {
    fonts: [
      `Montserrat\:300,400,600`,
      `Inconsolata\:400,900`,
    ],
  },
}

Ora i tuoi font saranno caricati automaticamente e disponibili in CSS, e i font-family 'Montserrat' e 'Inconsolata' sono pronti.

Puoi strutturare il tuo SCSS come preferisci, scegli il tuo framework, usa la tua struttura, tutto quello che vuoi, solo un consiglio: ogni volta che chiami un import, quel file viene compilato separatamente, quindi se ti piacciono le variabili quanto piacciono a me, mettile in un file separato e importale ovunque ti servano.

Puntavo a costruire una semplice one page, quindi ho iniziato con la navbar creando src/views/Navbar.js dove metterai il componente Navbar. Come dovrei scrivere qui? Gatsby è basato su React, scegli la sintassi che preferisci e inizia a digitare. Io userò la sintassi a funzione con Hooks ed Effects, tu puoi scegliere la sintassi che preferisci.

import React from 'react';
import '../styles/navbar.scss';

const Navbar = () => {
   return (
      <nav>
         <div className="menu">
            #1
         </div>
         <div className="kalizi">
            Kalizi
         </div>
         <div className="menu">
            #2
         </div>
      </nav>
   );
};

export default Navbar;

Come puoi vedere, non c'è nulla di nuovo se hai mai lavorato con react, solo una classe con del codice.

Ora prendiamo la nostra navbar e mettiamola nella nostra home, apriamo pages/index.js e modifichiamolo:

import React from 'react';
import Navbar from '../views/Navbar';

import '../styles/index.scss';

const Home = () => {
   return (
      <>
         <Navbar />
         <div>Hello from Gatsby!</div>
      </>
   )
};

export default Home;

In questo componente, ho usato il tag vuoto <></> che è una scorciatoia per React.Fragment. Se il tuo server di sviluppo è attivo (avviato con gatsby develop), dovresti vedere la tua navbar in alto e un bel "Hello from Gatsby!".

Costruire le sezioni

Ho iniziato creando una welcome section in views/home/WelcomeSection.js e il relativo foglio di stile in styles/home-sections.scss dove mettere tutti gli stili per le sezioni della home. Mettiamo insieme un po' di roba: per iniziare a costruire la welcome section volevo caricare due immagini, una foto per mostrare me stesso, e uno sfondo per rendere la pagina elegante. Caricare un'immagine è una pratica comune al giorno d'oggi, avrei potuto semplicemente mettere un file jpg da 4000x3000 e 5MB nella mia cartella asset pubblica, incorporarlo tramite un tag <img src="/assets/mybigfile.jpg"> e lasciare che il browser di qualcuno faticasse a caricarlo... ma non era il mio scopo, volevo sfruttare il plugin image di Gatsby che ti aiuta a caricare versioni diverse della stessa immagine in base al client che si sta connettendo grazie al tag <picture> che gestisce diversi source set. Prima di andare avanti, ho creato una cartella assets dentro la cartella src e ci ho messo l'immagine da caricare.

In modalità sviluppo Gatsby image caricherà le immagini dal filesystem, interrogandolo tramite GraphQL. Questo significa che la prima cosa da fare è far scoprire a GraphQL la nostra cartella assets usando il plugin Filesystem. Per caricare il plugin, torna di nuovo al nostro gatsby-config.js e aggiungi un altro oggetto al nostro array di plugin:

{
 resolve: 'gatsby-source-filesystem',
 options: {
  name: 'assets',
  path: `${__dirname}/src/assets/`,
 },
}

Questo oggetto dice a Gatsby di caricare il plugin filesystem, il plugin filesystem leggerà le opzioni e genererà tanti source node quanti sono i file nella directory passata come "path". Se vuoi dare un'occhiata ai file che vengono caricati puoi interrogarli tramite graphiql integrato in Gatsby, accessibile su localhost:8000/__graphql. Una volta dentro puoi lanciare una query per ottenere tutti i file indicizzati (non approfondirò come è scritta la query, mi limito a quella disponibile nella pagina della documentazione):

query AssetsQuery {
  allFile {
    edges {
      node {
        name
        extension
        relativePath
        modifiedTime
      }
    }
  }
}

Questo restituirà ogni node (file indicizzato) che il plugin filesystem ha trovato nella cartella, nel mio caso il risultato era:

{
  "data": {
    "allFile": {
      "edges": [
        {
          "node": {
            "name": "kalizi",
            "extension": "jpg",
            "relativePath": "kalizi.jpg",
            "modifiedTime": "2020-12-07T22:56:37.766Z"
          }
        },
        {
          "node": {
            "name": "keyboard",
            "extension": "jpg",
            "relativePath": "keyboard.jpg",
            "modifiedTime": "2020-12-06T23:34:52.796Z"
          }
        }
      ]
    }
  },
  "extensions": {}
}

Ora che i file sono correttamente indicizzati, possono essere caricati nella pagina web! A questo scopo, dobbiamo dire a gatsby di caricare i file nel nostro componente Home tramite una query, quindi apri pages/index.js e aggiungi la query caricando graphql:

import React from 'react';
import {graphql} from "gatsby";
// other omitted imports
const Home = ({data}) => {
    // code here
}
export default Home;
export const query = graphql`
  query {
    kaliziImage: file(relativePath: { eq: "kalizi.jpg" }) {
      childImageSharp {
        fixed(width: 256, height: 256) {
          ...GatsbyImageSharpFixed
        }
      }
    }
    bgImage: file(relativePath: { eq: "keyboard.jpg" }) {
      childImageSharp {
        fluid(maxWidth: 1920, quality: 100) {
          ...GatsbyImageSharpFluid
        }
      }
    }
  }
`;

C'è tanta roba tutta insieme, prendiamoci un momento per dividere e commentare quello che c'è:

  • abbiamo caricato graphql tramite import: niente altro da dire
  • abbiamo aggiunto un export di una costante chiamata query usando il tagged template: questa verrà caricata da Gatsby durante la fase di build che eseguirà la query, otterrà i risultati e li inietterà nel nostro componente Home usando l'attributo data delle nostre props, questo spiega perfettamente il punto successivo.
  • abbiamo aggiunto la destrutturazione delle props nel componente Home per prendere l'attributo data: lì troveremo le nostre immagini.
  • l'intera query contiene due sottoquery per due file (kaliziImage e bgImage) che verranno caricati ed elaborati per lo sharpening: il primo è un'immagine statica, quindi volevo affinarla a una dimensione fissa per adattarla allo stesso modo su ogni dispositivo, questo si può ottenere usando la clausola fixed; il secondo è un'immagine di sfondo, che non può essere fissa ma deve essere ridimensionata in modo fluido dentro il suo contenitore, questo si può ottenere usando la clausola fluid.

Se vuoi saperne di più su come funziona l'intero processo, puoi ottenere una spiegazione più approfondita nella pagina della documentazione di Gatsby.

Da qui, avremo le nostre immagini disponibili nel parametro data, volevo usarle nelle mie sezioni quindi le ho semplicemente passate di nuovo come props:

<WelcomeSection bgImage={data.bgImage.childImageSharp.fluid} kaliziImage={data.kaliziImage.childImageSharp.fixed} />

E le ho incorporate nella Welcome Section:

import React from 'react';
import Img from 'gatsby-image';
// other imports
const WelcomeSection = props => {
 return (
  <section className="welcome-section">
   <div className="bg-space">
    <Img fluid={props.bgImage} />
   </div>
   <div className="about-me">
    <Img fixed={props.kaliziImage} />
    // other code
   </div>
  </section>
 );
};

Nota che questo caricherà solo le immagini, se vuoi mostrarle in modo carino, devi sporcarti le mani con il CSS. Se vuoi conoscere tutte le proprietà del tag Img, di nuovo: documentazione.

Ora le immagini sono responsive, e per rendere tutto multilingua? Dobbiamo sfruttare gatsby-plugin-i18n. Inizia creando una cartella per le traduzioni e un file index dove mettere le lingue del sito (nel mio caso inglese e italiano) e la lingua predefinita src/i18n/index.js:

module.exports = {
   langs: ['en', 'it'],
   defaultLangKey: 'en'
};

Poi, di nuovo, facciamo sapere a gatsby-config che vogliamo usare il multilingua:

const languages = require('./src/i18n');
module.exports = {
 plugins: [
  // other plugins configuration
  {
   resolve: 'gatsby-plugin-i18n',
   options: {
    langKeyForNull: 'any',
    langKeyDefault: languages.defaultLangKey,
    useLangKeyLayout: true,
    prefixDefault: false,
   }
  }
 ]
};

Questo gestirà i path delle lingue come /en per il tuo sito, per gestirlo, mentre crei un file ti basta accodare la locale per quel file, quindi il nostro index deve essere diviso in due file index.en.js per /en e index.it.js per /it. Questo sito non ha bisogno di molti contenuti, quindi per le traduzioni mi limiterò a qualche file JSON da caricare tramite un require. Così ho creato un src/i18n/it/home.json e un src/i18n/en/home.json, e li ho popolati con i miei contenuti:

{
   "welcome": {
      "developer": "Backend&App Developer",
      ...
   }
}

Nel mio index.en.js ho aggiunto il relativo require:

const translations = require('../i18n/en/home');

E ho passato le traduzioni a ogni componente:

<WelcomeSection translations={translations.welcome} ... />

Dove ho semplicemente stampato ogni stringa con una normale stampa di stringa. È questa la soluzione migliore? Non lo so davvero, in questo caso è probabilmente la più veloce per andare online ma gatsby ha molte opzioni, molti plugin e la compatibilità con i18next e react-intl, quindi se ti serve di più, adatta il tuo progetto in modo diverso, dipende tutto da te!

Dopo aver costruito la prima sezione, ho iniziato a costruire una seconda sezione chiamata "Works section", quindi di nuovo, costruisci un componente in views/home/WorksSection.js e renderizzalo in index.[locale].js passando le traduzioni.

Ti ricordi che abbiamo parlato di smooth scrolling? È il momento di aggiungerlo! Apri di nuovo il nostro gatsby-config.js e aggiungilo ai plugin disponibili:

'gatsby-plugin-smoothscroll',

Come funziona? Ti dà una funzione scrollTo da chiamare dove vuoi attivare lo scroll, io volevo che quel trigger fosse nella nav, con i div del menu, quindi nel mio Navbar.js ho aggiunto:

import scrollTo from 'gatsby-plugin-smoothscroll';
// other code 
const Navbar = props => {
   return (
      <nav>
         <div className="menu" onClick={() => scrollTo('.welcome-section')}>
            
         </div>
         <div className="kalizi">
            Kalizi
         </div>
         <div className="menu" onClick={() => scrollTo('.work-section')}>
            #2
         </div>
      </nav>
   );
};

La sintassi di scrollTo è come quella di querySelector quindi il selettore fornito deve essere univoco per essere sicuri che lo scroll finisca dove vuoi che sia!

Il risultato mentre scrivevo era questo:

First section development

Un piccolo commento a riguardo:

  • Mi piace il viola, niente altro da dire su questo.
  • Le immagini sono caricate tramite il relativo plugin, questo significa che il caricamento è velocissimo, e tutte le manipolazioni per mettere l'immagine come sfondo per metà della pagina o per aggiungere il radius sono state fatte via CSS.

Configurare gli Analytics

Photo by Stephen Phillips - Hostreviews.co.uk on Unsplash

Parliamoci chiaro: ogni volta che metti un sito online e ci tieni, inizi a guardare ogni sistema di analytics che puoi o qualsiasi sistema di debug che hai installato per controllare cosa sta succedendo e cosa fanno gli utenti con esso!

Ho considerato solo Google Analytics per questa occasione, ma puoi cercare altri plugin da installare se preferisci (per esempio Yandex Metrika). Configurare gli analytics è davvero facile, basta aggiungere le opzioni all'array dei plugin in gatsby-config:

{
 resolve: `gatsby-plugin-google-analytics`,
 options: {
  trackingId: "yourtracking-id",
  head: false,
  anonymize: true,
  respectDNT: true,
  exclude: [],
  pageTransitionDelay: 0,
  // Defers execution of google analytics script after page load
  defer: false,
  sampleRate: 5,
  siteSpeedSampleRate: 10,
  cookieDomain: "your-domain.ext",
 },
},

Puoi modificare qualsiasi di queste opzioni, nel mio caso, semplicemente non volevo che il caricamento fosse posticipato e che gli IP fossero anonimizzati. Se vuoi modificare tutte le opzioni, devi consultare la documentazione. Niente altro, metti questo e sei pronto!

Un occhio alla SEO

Se hai mai lavorato con React, sai che uno dei modi migliori per gestire le cose nel tag <head> è usare React Helmet, in Gatsby, abbiamo il Gatsby plugin React Helmet per questo.

Prima di tutto, partiamo dal definire i metadata del sito nella nostra config per recuperarli come query graphql e strutturarli come JSON-LD

Dobbiamo costruire i metadata del nostro sito e metterli nel nostro gatsby-config:

module.exports = {
 siteMetadata: {
  pathPrefix: '/',
  title: 'Website title',
  titleTemplate: " | Basic template for completing the title",
  titleAlt: 'This is your alternative title',
  description: 'Your website description',
  headline: 'Website headling',
  url: 'https://yoursite.ext',
  siteUrl: 'https://yoursite.ext',
  defaultLanguage: 'en',
  logo: '/assets/logo.jpg',
  // JSONLD / Manifest
  favicon: '/assets/favicon.png', // Used for manifest favicon generation
  shortName: 'ManifestName', // shortname for manifest. 
  author: 'Your Name', // Author for schemaORGJSONLD
  themeColor: '#FFFFFF',
  backgroundColor: '#000000',
  // Facebook Attributes
  facebook: 'Sharing title',
  ogLanguage: 'it_IT',
 },
 plugins: [
  // Here goes the plugins definition
 ]
};

C'è un sacco di dati, e adesso? Crea una cartella component e un file in src/components/SEO.js:

import React from 'react';
import { Helmet } from 'react-helmet';
import { useStaticQuery, graphql } from 'gatsby';

const SEO = ({ pageTitle, desc, pathname, article, node, siteLanguage = null }) => {
	const { site } = useStaticQuery(query);
	const {
		buildTime,
		siteMetadata: {
			url,
			title,
			titleTemplate,
			description,
			headline,
			defaultLanguage,
			ogLanguage,
			themeColor,
			author,
			facebook,
		},
	} = site;

	let logo = site.siteMetadata.logo;

	siteLanguage = siteLanguage != null ? siteLanguage : defaultLanguage;

	const seo = {
		title: pageTitle ? (pageTitle + titleTemplate) : title,
		description: desc || description,
		image: `${url}${logo}`,
		url: `${url}${pathname || ''}`,
	};

	// schema.org in JSONLD format
	// https://developers.google.com/search/docs/guides/intro-structured-data
	// You can fill out the 'author', 'creator' with more data or another type (e.g. 'Organization')

	const schemaOrgWebPage = {
		'@context': 'http://schema.org',
		'@type': 'WebPage',
		url: url,
		headline,
		inLanguage: siteLanguage,
		mainEntityOfPage: url,
		description: description,
		name: pageTitle ? (pageTitle + titleTemplate) : title,
		author: {
			'@type': 'Person',
			name: author,
		},
		copyrightHolder: {
			'@type': 'Person',
			name: author,
		},
		copyrightYear: '2020',
		creator: {
			'@type': 'Person',
			name: author,
		},
		publisher: {
			'@type': 'Person',
			name: author,
		},
		datePublished: '2020-08-01T10:30:00+01:00',
		dateModified: buildTime,
		image: {
			'@type': 'ImageObject',
			url: `${url}${logo}`,
		},
	};

	// Initial breadcrumb list
	const itemListElement = [
		{
			'@type': 'ListItem',
			item: {
				'@id': url,
				name: 'Homepage',
			},
			position: 1,
		},
	];

	let schemaArticle = null;
	if (article) {
		schemaArticle = {
			'@context': 'http://schema.org',
			'@type': 'Article',
			author: {
				'@type': 'Person',
				name: author,
			},
			copyrightHolder: {
				'@type': 'Person',
				name: author,
			},
			copyrightYear: '2019',
			creator: {
				'@type': 'Person',
				name: author,
			},
			publisher: {
				'@type': 'Organization',
				name: author,
				logo: {
					'@type': 'ImageObject',
					url: `${url}${logo}`,
				},
			},
			datePublished: node.first_publication_date,
			dateModified: node.last_publication_date,
			description: seo.description,
			headline: seo.title,
			inLanguage: siteLanguage,
			url: seo.url,
			name: seo.title,
			image: {
				'@type': 'ImageObject',
				url: seo.image,
			},
			mainEntityOfPage: seo.url,
		};
		// Push current post into breadcrumb list
		itemListElement.push({
			'@type': 'ListItem',
			item: {
				'@id': seo.url,
				name: seo.title,
			},
			position: 2,
		});
	}

	const breadcrumb = {
		'@context': 'http://schema.org',
		'@type': 'BreadcrumbList',
		description: 'Breadcrumbs list',
		name: 'Breadcrumbs',
		itemListElement,
	};

	return (
		<>
			<Helmet title={seo.title}>
				<html lang={siteLanguage} />
				<meta charSet="UTF-8" />
				<meta name="author" content={author} />
				<meta name="description" content={seo.description} />
				<meta name="image" content={seo.image} />
				<meta name="theme-color" content={themeColor} />
				<meta name="viewport" content="width=device-width, initial-scale=1.0" />
        {/* Favicon here */}
				{/* Insert schema.org data conditionally (webpage/article) + everytime (breadcrumbs) */}
				{!article && <script type="application/ld+json">{JSON.stringify(schemaOrgWebPage)}</script>}
				{article && <script type="application/ld+json">{JSON.stringify(schemaArticle)}</script>}
				<script type="application/ld+json">{JSON.stringify(breadcrumb)}</script>
			</Helmet>
		</>
	)
};
export default SEO;

const query = graphql`
	query SEO {
		site {
			buildTime(formatString: "YYYY-MM-DD")
			siteMetadata {
				url
				title
				titleTemplate	
				description
				logo
				headline
				defaultLanguage
				ogLanguage
				themeColor
				author
				facebook
			}
		}
	}
`;

Vedi su GitHub Gist

Questo è un componente grande, recupera i dati dalla nostra config tramite la query GraphQL e pubblica quei dati tramite Helmet nel tag header usando il formato degli standard web (puoi scoprire di più qui). Tutto in quel componente è altamente personalizzabile, puoi modificare quasi tutto per ottenere ciò che vuoi pubblicare.

Se vuoi anche aggiungere una Favicon, costruiamo un componente Favicon e incorporiamolo nel commento del componente SEO:

import React from 'react';

const Favicon = () => (
    <>
        <link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png" />
        <link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-icon-60x60.png" />
        <link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-icon-72x72.png" />
        <link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-icon-76x76.png" />
        <link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-icon-114x114.png" />
        <link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-icon-120x120.png" />
        <link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-icon-144x144.png" />
        <link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-icon-152x152.png" />
        <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-icon-180x180.png" />
        <link rel="icon" type="image/png" sizes="192x192"  href="/favicon/android-icon-192x192.png" />
        <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
        <link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png" />
        <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
        <meta name="msapplication-TileColor" content="#24252a" />
        <meta name="msapplication-TileImage" content="/favicon/ms-icon-144x144.png" />
        <meta name="theme-color" content="#24252a" />
    </>
)

export default Favicon;

Vedi su GitHub Gist

Puoi anche fare qualcosa di davvero basilare per l'open-graph di Facebook:

import React from 'react';
import { Helmet } from 'react-helmet';

const Facebook = ({ url, name, type, title, desc, image, locale }) => (
	<Helmet>
		{name && <meta property="og:site_name" content={name} />}
		<meta property="og:locale" content={locale} />
		<meta property="og:url" content={url} />
		<meta property="og:type" content={type} />
		<meta property="og:title" content={title} />
		<meta property="og:description" content={desc} />
		<meta property="og:image" content={image} />
		<meta property="og:image:alt" content={desc} />
	</Helmet>
);

export default Facebook;

Infine, ora che i tuoi componenti sono pronti, apri il tuo index.[locale].js e aggiungi

import SEO from '../components/SEO/SEO';
...
<SEO siteLanguage="_YOUR_LOCALE_" />
...

Nota bene: questa è una possibile implementazione, quasi sicuramente non la migliore, quindi sentiti libero di usare qualcosa di diverso da questa, puoi usare per esempio gatsby-plugin-seo, i componenti di gatsby-theme-seo, i componenti di gatsby-seo-example, oppure puoi semplicemente essere creativo e scrivere il tuo componente.

Un'altra cosa carina per l'indicizzazione web è la sitemap. Gatsby conosce tutte le tue pagine, quindi possiamo lasciargliela generare automaticamente tramite il plugin sitemap, nel mio caso particolare la sitemap non sarà così lunga e non ho bisogno di alcuna modifica quindi aggiungere gatsby-plugin-sitemap al gatsby-config.js farà il lavoro, ma se vuoi di più puoi leggere la doc del plugin!

Pagina non trovata 🛑

E se... aprissi una pagina inesistente come localhost:8000/aaaaaa?

Gatsby development 404

Dovremmo aspettarci questo risultato, è un normale errore HTTP 404 e gatsby ci aiuta a personalizzarlo, di nuovo, c'è la documentazione di gatsby per questo. Ci basta aggiungere un file: src/pages/404.js. Lascio qui una pagina basilare:

import React from 'react';
import Navbar from '../views/Navbar';

const navTranslations = require('../i18n/en/navbar');

const NotFound = () => {
    return (
        <>
            <Navbar translations={navTranslations} />
            <p style={{ paddingTop: '70px', color: 'white', textAlign: 'center', fontWeight: 700 }}>
                DEAD PAGE HERE ☠️
            </p>
        </>
    );
};

export default NotFound;

Che produrrà:

Ora che il 404 funziona, sta a te personalizzarlo, Gatsby lo analizzerà e costruirà un 404.html da impostare come 404 statico da servire dal tuo web server o hosting.

Il deploy

Photo by Lenin Estrada on Unsplash

Ora siamo pronti, la pubblicazione è a un tiro di schioppo da qui! Ferma il server di sviluppo lanciato con gatsby develop. Alcuni package a volte combinano un pasticcio con la cache di gatsby, quindi la prima cosa da fare è una pulizia della cache con il comando

gatsby clean

Ora l'ultimo passo, costruisci la versione di produzione tramite

gatsby build

Gatsby ha una bella guida su come mettere il tuo sito online. Il tempo di build dipenderà dal tuo hardware, nel mio caso ci sono voluti appena 21.2506825 sec, la cosa curiosa è che puoi vedere tutto sul processo di build:

success open and validate gatsby-configs - 0.041s
success load plugins - 0.838s
success onPreInit - 0.030s
success delete html and css files from previous builds - 0.005s
success initialize cache - 0.005s
success copy gatsby files - 0.073s
success onPreBootstrap - 0.014s
success createSchemaCustomization - 0.006s
success Checking for changed pages - 0.002s
success source and transform nodes - 0.105s
success building schema - 0.333s
info Total nodes: 53, SitePage nodes: 1 (use --verbose for breakdown)
success createPages - 0.004s
success Checking for changed pages - 0.001s
success createPagesStatefully - 0.075s
success update schema - 0.020s
success onPreExtractQueries - 0.001s
success extract queries from components - 0.391s
success write out redirect data - 0.004s
success onPostBootstrap - 0.001s
info bootstrap finished - 5.393s
success run static queries - 0.043s - 1/1 23.29/s
success run page queries - 0.214s - 4/4 18.71/s
success write out requires - 0.042s
success Building production JavaScript and CSS bundles - 13.014s
success Rewriting compilation hashes - 0.003s
success Building static HTML for pages - 2.363s - 4/4 1.69/s
success Generating image thumbnails - 15.759s - 15/15 0.95/s
success onPostBuild - 0.010s
info Done building in 21.2506825 sec

Questo processo produrrà una cartella public con tutti i tuoi file. Ora il sito è pronto, ti basta prendere il contenuto della cartella public e pubblicarlo sul tuo hosting. Puoi usare un hosting gestito se non sai come impostare la tua architettura, puoi usare un VPS a basso costo se vuoi impostare il tuo stack software personalizzato oppure puoi passare a qualcosa come Github Pages o Heroku. Tutto ciò che viene dopo il deploy sta a te!

Audit della versione di produzione di Gatsby

Vuoi testare il tuo sito per le pratiche web comuni e controllare se è in linea con gli standard web più diffusi? Chrome ti copre le spalle con Lighthouse, un'estensione per fare l'audit di un sito/applicazione web focalizzata su Performance, Accessibility, Best practices, SEO e PWA.

Il primo test arriva dopo la build, pulisci la tua cache con gatsby clean, costruisci la tua versione di produzione con gatsby build e avvii il server di test per la versione di produzione con gatsby serve. Il sito è ora disponibile su localhost:9000, per fare l'audit con Lighthouse ti basta aprire i Developer Tools e saltare all'estensione Lighthouse, ora scegli le opzioni che vuoi testare, la versione (Mobile/Desktop) e lascia che Lighthouse generi il report. Nel mio caso, per mobile, con tutte le opzioni selezionate il risultato è questo:

Lighthouse report for local gatsby serve server

A mio parere, i risultati erano buoni. Il risultato PWA è zero perché il sito non è stato progettato per essere una PWA. Il dettaglio completo sul punteggio di performance era principalmente incentrato su:

  • HTTP2 non era implementato → è un server di test locale, va bene così.
  • Nessuna policy di caching → idem qui, è un server di test.

Ora prendi la versione compilata e mettila online in un ambiente ottimizzato, nel mio caso ho messo il sito in un VPS con Nginx (configurato per cacheare i contenuti statici a lungo) supportato da Cloudflare. I risultati del mettere il sito in questo stack sono stati questi:

Lighthouse report for deployed server with tweaked Nginx and Cloudflare

Questi risultati di performance sono ovviamente migliori dei risultati locali perché il test è stato fatto in un ambiente ottimizzato. Provando l'audit sulla versione Desktop la performance sale a 99 invece di 97. Se vuoi ottenere tutti 100 su 100, devi controllare ogni dettaglio descritto nel report, ma di nuovo, sta a te, per me, questi livelli andavano bene!