Auf dieser Seite fasse den Inhalt meiner Videos zum Thema Gatsby und Ghost zusammen. Da es meiner Meinung nach nicht genügend deutschsprachige Anleitungen dazu gibt, habe ich eins erstellt. Es geht darum, wie man von Grund auf seine erste Gatsby.js Seite erstellt, Ghost auf einem VPS installiert und als headless CMS verwendet und auch, wie man die Seite dann online in der Gatsby-Cloud veröffentlicht. Ich zeige alle Schritte, die dazu nötig sind. Die Videos haben eine Gesamtlänge von zwei Stunden und befinden sich (demnächst) bei YouTube (aufgrund der Größe und Reichweite möchte ich diese nicht selbst hosten), dementsprechend gehe ich nicht auf jede Kleinigkeit ein, sondern versuche eher einen Überblick zu geben.
Auf YouTube habe ich eine Playlist erstellt. Bisher sind der erste Teil und der zweite Teil online (Links zu YouTube). Der dritte Part folgt noch.
Bevor die lokale Gatsby-Seite erstellt werden, müssen ein paar Programme vorinstalliert werden. Wir brauchen Node.js, Git sowie VSCode (oder einen anderen Texteditor).
Im Terminal (als Administrator ausgeführt) installieren wir die Gatsby-CLI.
npm install -g gatsby-cli
Die Gatsby-CLI ist damit installiert.
Zunächst muss git/bash geöffnet werden (entweder Standalone oder in VSCode)
Der Befehl gatsby new
erstellt eine neue Gatsby Seite.
Die Gatsby-Seite ist damit erstellt.
Zum Starten des Live-Servers muss man der anzeigten Anweisung folgen.
cd namedesneuenverzeichnisses# bzw cd vollständiger Pfad vom Verzeichnis, an dem die neue Seite erstellt wurdenpm run develop
Der Live-Server wird gestartet. Mit Strg + C wird er wieder beendet.
Nachdem das Projekt geöffnet wird, können nun die einzelnen Elemente bearbeitet werden.
Der Inhalt der index.js im Verzeichnis /src/pages kann gelöscht werden, wir wollen stattdessen unseren eigenen hinzufügen.
/src/pages/index.js
:
import * as React from 'react'const IndexPage = () => {return (<div><h1> Hallo Welt </h1><p> Hier könnte Ihre Werbung stehen 😃 </p></div>)}export default IndexPage
Innerhalb der Return-Funktion kann dann HTML-in-JS-Code genutzt werden. Wird der Development-Server wieder gestartet (entweder gatsby develop
oder npm run develop
), sind die Änderungen zu sehen.
Für das Styling kann CSS genutzt werden. Dazu eine CSS-Datei erstellen und diese bearbeiten.
mkdir src/stylestouch src/styles/global.css
Ich erstelle eine global.css in einem Styles-Ordner
In diese kommt dann gewöhnlicher CSS-Code.
/src/styles/global.css
:
body {margin: 0 auto;max-width: 50em;}h1 {color: slateblue;}
Der CSS-Code kann in der jeweiligen Gatsby-Seite importiert werden oder wir nutzen den gatsby-browser. Dazu gatsby-browser.js im Root-Verzeichnis erstellen.
touch gatsby-browser.js
In die Datei folgenden Code einfügen:
import './src/styles/global.css'
Sobald der Server neu gestartet wird, sind die Änderungen durch die CSS-Datei zu sehen.
Wir erstellen eine neue Unterseite im Pages-Verzeichnis.
touch src/pages/about.js
Und fügen da wieder den Code ein, denn wir benötigen.
import * as React from 'react'const About = () => {return (<div><h1> About </h1><p> Das ist die About-Seite 😃 </p><p> Einen Link zur Hauptseite gibt es leider noch nicht </p></div>)}export default About
Diese Seite ist nach dem Speichern unter localhost:8000/about zu sehen, sofern der Gatsby-Server noch läuft.
Mit Gatsby-Link lassen sich interne Links einfügen. Wir müssen es nur importieren. Dazu ändern wir die About-Seite wieder.
import * as React from 'react'import { Link } from 'gatsby'const About = () => {return (<div><h1> About </h1><p> Das ist die About-Seite 😃 </p><p><Link to='/'> Home </Link> ist nun verlinkt.</p></div>)}export default About
Die Gatsby-Browser.js Datei kann ich wieder leeren, die brauchen wir vorerst nicht mehr. Es ist empfehlenswert wiederverwendbare Komponenten zu nutzen. Dazu erstellen wir einen neuen Ordner.
mkdir src/componentstouch src/components/layout.js
In die Layout.js füge ich folgenden Code ein:
import * as React from "react";import { Link } from "gatsby";const Layout = ({ pageTitle, children }) => {return (<main><title>{pageTitle}</title><nav><ul><li><Link to="/">Home</Link></li><li><Link to="/about">About</Link></li><li><a href="https://deployn.de" target="_blank">Deployn</href></li></ul></nav><h1>{pageTitle}</h1>{children}</main>);};export default Layout;
Der Link im <a>
-Tag ist deutlich langsamer (und das liegt nicht nur an der Geschwindigkeit meiner Seite).
Jetzt müssen die Seiten angepasst werden.
import * as React from 'react'import Layout from '../components/layout'const About = () => {return (<Layout pageTitle='About'><p> Das ist die About-Seite 😃 </p></Layout>)}export default About
import * as React from 'react'import Layout from '../components/layout'const IndexPage = () => {return (<Layout pageTitle='Home'><p> Das ist nicht die About-Seite 😃 </p></Layout>)}export default IndexPage
Dadurch habe ich eine Layout-Komponente, die ich wieder verwende.
Ich erstelle eine module.css.
touch src/components/layout.module.css
Da kommt CSS-Code rein.
.container {margin: 0 auto;max-width: 50em;}.heading {color: slateblue;}
Dieses CSS importieren wir in der Komponente.
import * as React from "react";import { Link } from "gatsby";import { container, heading } from "./layout.module.css";const Layout = ({ pageTitle, children }) => {return (<main className={container}><title>{pageTitle}</title><nav><ul><li><Link to="/">Home</Link></li><li><Link to="/about">About</Link></li><li><a href="https://deployn.de" target="_blank">Deployn</href></li></ul></nav><h1 className={heading}>{pageTitle}</h1>{children}</main>);};export default Layout;
Mit Gatsby-Plugin-Image lassen sich Bilder einfügen und dabei optimieren. Dazu muss das Plugin installiert sein.
npm install gatsby-plugin-image
Es muss auch in der Gatsby-Config als Plugin gelistet sein.
Wir möchten ein statisches Bild einfügen, zunächst fügen wir irgendein Bild in das Projekt ein.
mkdir src/images# hier nun ein Bild einfügen
Dieses Bild soll auf der About-Seite geladen werden. Dazu StaticImage importieren und nutzen.
import * as React from 'react'import { StaticImage } from 'gatsby-plugin-image'import Layout from '../components/layout'const About = () => {return (<Layout pageTitle='About'><p> Das ist die About-Seite 😃 </p><StaticImage src='../images/bild.jpg' /></Layout>)}export default About
Es können noch ein paar Optionen genutzt werden.
import * as React from 'react'import { StaticImage } from 'gatsby-plugin-image'import Layout from '../components/layout'const About = () => {return (<Layout pageTitle='About'><p> Das ist die About-Seite 😃 </p><StaticImagesrc='../images/bild.jpg'alt='Eine Bildbeschreibung'width='200'placeholder='tracedSVG'/></Layout>)}export default About
Die Dokumentation erklärt das Plugin ausführlicher.
Für das Build muss gegebenenfalls die Site-URL in der Gatsby-config eingetragen werden.
module.exports = {siteMetadata: {title: "Deployn",siteUrl: "https://deployn.de",},plugins: ["gatsby-plugin-gatsby-cloud","gatsby-plugin-image","gatsby-plugin-react-helmet","gatsby-plugin-sitemap",{resolve: "gatsby-plugin-manifest",options: {icon: "src/images/icon.png",},},"gatsby-plugin-sharp","gatsby-transformer-sharp",{resolve: "gatsby-source-filesystem",options: {name: "images",path: "./src/images/",},__key: "images",},},],};
Nun kann der Build-Prozess gestartet werden.
npm run build
Im Public-Ordner wird jetzt eine Seite erstellt. Die Seite kann veröffentlicht werden, indem der Inhalt auf einen Webserver gepackt wird.
Als Nächstes wird ein CMS benötigt. Ich möchte dafür Ghost.js headless benutzen. Hat man einen Server mit Traefik als Proxy Manager bereits eingerichtet, kann diese Anleitung genutzt werden. Ansonsten fasse ich hier noch mal die Schritte zusammen.
Möchte man weder Ghost lokal installieren (schwierig, wenn es immer online sein soll und es mehrere Autoren gibt), noch aus finanziellen Gründen Ghost Pro nutzen, bleibt nur das Hosting auf einem Server.
Ich nutze dafür hier einen Server von Netcup (Affiliate-Link). Für Neukunden habe ich hier Gutscheine für die Server. Nutzt man den Server nur für Ghost reicht der kleinste aus, jedoch hat der auch wenig Speicherplatz.
Auf dem Server installiere ich Ubuntu 20.04.
Funktioniert es auch mit den anderen Images? Ja, sogar fast identisch, weil ich Docker nutze.
Ich stelle eine SSH Verbindung zum Server her. Bei Windows muss ich dafür vorerst den OpenSSH-Client aktivieren. Dazu unter “Apps und Features” auf „optionale Features“ drücken.
Dort lässt sich der OpenSSH-Client installieren (nicht verwechseln mit OpenSSH-Server).
Jetzt ein Terminal aufrufen.
ssh root@185.183.158.137
Die IP-Adresse muss die des eigenen Servers sein
apt updateapt upgrade
Damit updaten wir den Server aus den Ubuntu Datenpaketen
useradd -m -G sudo jewgenipasswd jewgeni
Damit erstellen wir einen neuen Nutzer, fügen ihn der sudo-Gruppe hinzu und geben ihm ein Passwort
reboot
Damit starten wir den Server neu
ssh jewgeni@185.183.158.137
Diesmal verbinden wir uns mit dem neu erstellten Nutzer
Möglichkeit 1 ist es, sich einfach Docker aus den Standard-Ubuntu-Paketen zu installieren.
sudo apt install docker docker-compose
Nachteil: Es ist nicht die neuste Version.
Möglichkeit 2 ist es, die offizielle Anleitung zu befolgen.
sudo apt-get remove docker docker-engine docker.io containerd runcsudo apt-get updatesudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-releasecurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgecho \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullsudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.io
Damit ist Docker installiert
sudo gpasswd -a jewgeni docker
Damit fügen wir den Benutzer in die Docker-Gruppe hinzu Ich installiere auch noch Lazydocker.
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
Ich erstelle einen Docker Ordner und gebe der Gruppe Docker alle Rechte daran.
mkdir dockersudo apt install aclsudo setfacl -Rdm g:docker:rwx dockersudo setfacl -Rm g:docker:rwx dockersudo chmod -R 775 dockersudo chown -R jewgeni:docker docker
Als Nächstes benötige ich eine Domain für den Server. Auch hier verwende ich Netcup (Affiliate-Link). Eine Domain mit “.de” als TLD kostet 5 € im Jahr (auch in den folgenden Jahren).
In den DNS Einstellungen der Domain setze ich einen Eintrag für Root (@), sowie einen mit einer Wildcard (*), nachdem ich die Default-Werte gelöscht habe.
Nach dem Speichern vergehen bis zu 48 Stunden, bevor der Eintrag wirklich Auswirkungen hat.
Zur einfacheren Verwaltung eines Nginx Reverse-Proxys und Let’s Encrypt Zertifikaten möchte ich den Nginx-Proxy-Manager auf dem Server installieren.
ssh jewgeni@185.183.158.137
Verbindung zum Server wiederherstellen
cd ~/dockersudo docker network create npm_netmkdir nginx-proxy-managercd nginx-proxy-managermkdir data/mysql -pmkdir letsencrypttouch .envecho "DB_PWD=$(openssl rand -hex 16)" >> .envecho "DB_ROOT_PWD=$(openssl rand -hex 16)" >> .envtouch docker-compose.yml
Wir haben damit ein neues Docker-Netzwerk und im neuen nginx-proxy-manager Ordner weitere Verzeichnisse und zwei Dateien erstellt Die Docker-Compose Datei muss befüllt werden. Man kann dazu auch VSCode nutzen.
nano docker-compose.yml
version: "3.7"networks:npm_net:external:name: npm_netinternal:external: falsedefault:driver: bridgeservices:nginx-proxy-manager:container_name: nginx-proxy-managerimage: jc21/nginx-proxy-manager:2.9.3restart: unless-stoppednetworks:- npm_net- internalports:- "80:80"- "443:443"- "81:81"environment:DB_MYSQL_HOST: npm_dbDB_MYSQL_PORT: 3306DB_MYSQL_USER: npmDB_MYSQL_PASSWORD: $DB_PWDDB_MYSQL_NAME: npmDISABLE_IPV6: truevolumes:- ./data:/data- ./letsencrypt:/etc/letsencryptdepends_on:- npm_dbnpm_db:container_name: npm_dbimage: mariadb:10.6.1restart: unless-stoppednetworks:- internalenvironment:MYSQL_ROOT_PASSWORD: $DB_ROOT_PWDMYSQL_DATABASE: npmMYSQL_PASSWORD: $DB_PWDMYSQL_USER: npmvolumes:- ./data/mysql:/var/lib/mysql
docker-compose up -d
Die Logs können in Lazydocker angesehen werden.
lazydocker# bzw. sudo lazydocker
Die Anmeldung unter der IP-Adresse und Port 81 erfolgt mit admin@example.com und dem Passwort “changeme”.
Wir benötigen einen neuen Proxy Host.
Als Domain muss nun die Subdomain eingetragen werden, unter der man diese GUI des nginx-proxy-managers erreichen möchte. Hostname ist localhost. Port ist 81, sofern diese Einstellung in der docker-compose Datei nicht geändert wurde.
Unter den SSL-Einstellungen muss ein neues Zertifikat angefordert werden.
Sobald es gespeichert ist, lässt sich der nginx-proxy-manager über die Subdomain aufrufen.
Wir installieren eine Firewall auf dem Server (optional).
ssh jewgeni@185.183.158.137
Verbindung zum Server wiederherstellten
sudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw allow 22/tcpsudo ufw default deny incomingsudo ufw enablesudo reboot
Es muss ein anderer Port gewählt werden, wenn SSH nicht über Port 22 genutzt wird, sonst sperrt man sich selbst aus Docker sorgt dafür, dass die Firewall nicht richtig blockiert. Bei Github gibt es eine Lösung:
sudo nano /etc/ufw/after.rules
In diese Datei wird unten folgender Code angefügt.
# BEGIN UFW AND DOCKER*filter:ufw-user-forward - [0:0]:ufw-docker-logging-deny - [0:0]:DOCKER-USER - [0:0]-A DOCKER-USER -j ufw-user-forward-A DOCKER-USER -j RETURN -s 10.0.0.0/8-A DOCKER-USER -j RETURN -s 172.16.0.0/12-A DOCKER-USER -j RETURN -s 192.168.0.0/16-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12-A DOCKER-USER -j RETURN-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "-A ufw-docker-logging-deny -j DROPCOMMIT# END UFW AND DOCKER
Jetzt muss ich noch die Routen erlauben.
sudo ufw route allow 80/tcpsudo ufw route allow 443/tcpsudo reboot
Die Installation von Ghost lässt sich durch Docker sehr einfach durchführen. Dazu verbinde ich mich wieder mit dem Server.
ssh jewgeni@185.183.158.137
Verbindung zum Server wiederherstellten
cd ~/dockermkdir ghostcd ghostmkdir contentmkdir dbtouch .envtouch docker-compose.ymlecho "DB_PWD=$(openssl rand -hex 20)" >> .env
nano docker-compose.yml
version: '3.7'networks:npm_net:external:name: npm_netinternal:external: falsedefault:driver: bridgeservices:ghost:container_name: ghostimage: ghost:4.6.4restart: unless-stoppednetworks:- npm_net- internalsecurity_opt:- no-new-privileges:trueenvironment:- database_client=mysql- database_connection_host=ghost-db- database_connection_user=root- database_connection_password=$DB_PWD- database_connection_database=ghost- url=https://ghost.pixan.devolumes:- ./content:/var/lib/ghost/contentdepends_on:- ghost-dbghost-db:container_name: ghost-dbimage: mariadb:10.6.1restart: unless-stoppednetworks:- internalsecurity_opt:- no-new-privileges:trueenvironment:MYSQL_ROOT_PASSWORD: $DB_PWDvolumes:- ./db:/var/lib/mysql
docker-compose up -d
Als Nächstes muss im Nginx-Proxy-Manager ein neuer Proxy-Host für Ghost eingerichtet werden. Der Hostname entspricht dem Namen des Services (in meinem Fall ghost), der Port von Ghost ist 2368.
Unter den SSL Einstellungen wieder ein neues Zertifikat anfordern (oder auf DNS-Challenge umstellen).
Nach dem Speichern müsste das Ghost CMS über die URL erreichbar sein. Unter domain.de/ghost befindet sich die Administration. Hier muss man sich einen Account anlegen.
Ghost erstellt (wie auch z. B. WordPress) automatisch ein Frontend. Nutzt man kein Gatsby, kann man so auch einen Blog direkt betreiben. Wir benötigen aber kein Frontend, dass von Unbekannten besucht werden soll. Deshalb können wir die Seite unter den Einstellungen auf privat stellen.
Wenn man den Nutzer Ghost löscht, sollten zum Test mindestens zwei Testposts erstellt und veröffentlicht werden. Sonst haben wir nichts, worüber das Query später laufen kann.
Um Ghost als Quelle zu nutzen, muss das Gatsby Source Ghost Plugin in der Gastby-Seite installiert werden.
npm install gatsby-source-ghost
Das Plugin benötigt den content-Api-Key, um sich verifizieren zu können. Diesen kann man in der Ghost-Instanz unter dem Menüpunkt “Integrations” erstellen.
Nachdem ein Name vergeben wird, ist der API-Key zu sehen.
Anschließend muss das Plugin inklusive der eigenen Daten der Gatsby-Config Datei hinzugefügt werden.
module.exports = {siteMetadata: {title: 'Pixan',siteUrl: 'https://pixan.de'},plugins: ['gatsby-plugin-gatsby-cloud','gatsby-plugin-image','gatsby-plugin-react-helmet','gatsby-plugin-sitemap',{resolve: 'gatsby-plugin-manifest',options: {icon: 'src/images/icon.png'}},'gatsby-plugin-sharp','gatsby-transformer-sharp',{resolve: 'gatsby-source-filesystem',options: {name: 'images',path: './src/images/'},__key: 'images'},{resolve: `gatsby-source-ghost`,options: {apiUrl: `https://ghost.pixan.de`,contentApiKey: `74bb1b0d67765995e687357669`}}]}
Im Root-Verzeichnis muss nun eine gatsby-node.js erstellt werden.
const path = require(`path`)exports.createPages = async ({ graphql, actions, reporter }) => {const postTemplate = path.resolve(`./src/templates/post.js`)// Query Ghost dataconst result = await graphql(`{allGhostPost(sort: { order: ASC, fields: published_at }) {edges {node {slug}}}}`)// Handle errorsif (result.errors) {reporter.panicOnBuild(`Error while running GraphQL query.`)return}if (!result.data.allGhostPost) {return}// Create pages for each Ghost postconst items = result.data.allGhostPost.edgesitems.forEach(({ node }) => {node.url = `/${node.slug}/`actions.createPage({path: node.url,component: postTemplate,context: {slug: node.slug}})})}
Jetzt benötigen wir die Template-Datei für die Posts, die dynamisch aus den GhostPosts erstellt werden sollen. Wir speichern sie in den Ordnern src/templates.
cd srcmkdir templatescd templatestouch post.js
import React from 'react'import { graphql } from 'gatsby'const Post = ({ data }) => {const post = data.ghostPostreturn (<><article className='post'>{post.feature_image ? (<img src={post.feature_image} alt={post.title} />) : null}<h1>{post.title}</h1><section dangerouslySetInnerHTML={{ __html: post.html }} /></article></>)}export default Postexport const postQuery = graphql`query ($slug: String!) {ghostPost(slug: { eq: $slug }) {titleslugfeature_imagehtml}}`
Damit werden die Posts dynamisch erstellt.
Als Beispiel möchte ich, dass alle Posts als Link auf der Blogseite gezeigt werden. Dazu verändern wir die blog.js-Datei.
import * as React from 'react'import { graphql, Link } from 'gatsby'import Layout from '../components/layout'const Blog = ({ data }) => {return (<Layout pageTitle='Blog'><p>Hier könnte ein Blog sein.</p><ul>{data.allGhostPost.edges.map(({ node }) => (<li><Link to={`/${node.slug}`}>{node.title}</Link></li>))}</ul></Layout>)}export default Blogexport const postSlug = graphql`query MyQuery {allGhostPost {edges {node {slugtitle}}}}`
Über die Map-Funktion werden alle Slugs nach dem dazugehörigen Titel durchsucht. Der Titel wird angezeigt, der Link führt selbstverständlich zum Slug.
Zunächst benötige ich ein Repository. Dazu nutze ich GitHub. Nach der Anmeldung kann ein neues (privates) Repository erstellt werden.
In meinem Gatsby Projekt initiiere ich Git. Für den pull benötige ich die URL des Repositorys.
git initgit pull https://github.com/Benutzername/Name.gitgit remote add origin https://github.com/Benutzername/Name.git
In die Git-Ignore-Datei füge ich zumindest .cache, .vscode, node-modules sowie public hinzu, damit diese Dateien oder Verzeichnisse nicht hochgeladen werden.
Vor dem Upload muss Git konfiguriert werden.
git config --global user.name "Github Name"git config --global user.email eigenemail@gmail.com
Nun alle Dateien auswählen, damit sie in den Staged Changes auftauchen. Über den Haken bestätigen und einen Kommentar schreiben.
Über das Symbol unten können die Änderungen synchronisiert werden.
Anschließend müsste in Github der Code in master-branch zu sehen sein.
Jetzt einen kostenlosen Account in der Gatsby-Cloud erstellen. Beim Hinzufügen einer neuen Seite aus einem Github Repository wird ein Webhook angezeigt. Den kopieren wir uns.
Dieser kann wiederum den Integrationen als Custom Integration in Ghost hinzugefügt werden.
Letztlich müsste indessen die Seite erstellt sein.
Unter den Einstellungen lässt sich die eigene Domain hinzufügen.
Die angezeigten DNS-Einstellungen müssen gesetzt werden.
Damit ist die Einrichtung soweit fertig und die Seite ist (eventuell erst nach ein paar Stunden) unter der eigenen Domain erreichbar.