Skip to content

BenGoetemann/ratfuerkunst

Repository files navigation

This is LEAF

Leaf (Literally Easy As Fuck) ist eine Art "Framework", mit der sich Jamstack Websites so einfach und schnell wie möglich bauen lassen. Es ist sozusagen ein weißes Blatt - und ja ich weiß "Leaf" ist nicht DAS Blatt hehe - auf dem fast alles, außer (zurzeit noch) Login, umgesetzt werden kann.

Table of Contents



Getting Started 🚀

Dieses Template wurde erstellt um Jamstack Anwendungen umzusetzen. Es basiert auf folgenden Technologien:


Projekt installieren

Um das Projekt zum Laufen zu bringen muss folgendes gemacht werden:

  1. Dependencies installieren mit yarn.
  2. Ein neues Repo auf Github anlegen und mit dem Projekt verbinden.
  3. CI/CD einrichten, indem man auf Netlify das Repo verbindet.

Projekt starten

Wenn man das Projekt starten möchte gibt es zwei Möglichkeiten:

  1. Wenn man keine Netlify Functions nutzt yarn dev.
  2. Wenn man Netlify Functions nutzt yarn netlify dev.

Auch wenn yarn netlify dev genutzt wird um das Projekt zu starten, wird ein Localhost:3000 aufgesetzt.


Technologien entfernen

Sollte man diverse Komponenten nicht benötigen, sollte man diese in diesen Schritten entfernen:

  1. Dependencies löschen mit yarn remove [package_name]
  2. ~node_modules/ löschen
  3. Die jeweiligen Ordner löschen und ggf. die Verweise in der ~nuxt.config.
  4. yarnausführen.

Style-Personalisierung

Um das Corporate Design eines Auftraggebers in das Template zu überführen müssen folgende Schritte erledigt werden:

  • Logo in ~/assets/images/ci ablegen.
  • Logo in der Logo.vue Komponente unter ~/components/figures austauschen.
  • Font in den Stylings unter ~/layouts/default.vue importieren und in html als Font-Family setzen.
  • Farben und Spacings in der tailwind.config anpassen.

Ab dann müssen die Stylings in den jeweiligen Komponenten angepasst werden.



Key Concepts

Nachfolgend werden wichtige Konzepte dieses Templates beschrieben, die zu einem tieferen Verständnis führen sollen, um schnell Anpassungen durchführen zu können.


Components verstehen

Dieses Template bietet vorgefertigte Komponenten, die genutzt werden können, um eine Website wue Lego zusammenzubauen. Diese können mithilfe von Props und Slots personalisiert werden. Komponenten bestehen aus:

  • Elements - Vorgefertige Website-Elemente wie Buttons, eine NavBar etc.
  • Figures - Image-Komponenten, die sich ggf. an mehreren Stellen befinden, wie bspw. Logo
  • Navigation Items - Hilfskomponenten, die speziell in Navigations-Komponenten verwendet werden.
  • Sections - Bereichs-Komponenten, die Responsivness, Positioning und Spacings zentralisieren.
  • Selections - Komponenten, die eine kontrollierte Ansammlung von anderen Komponenten bilden.

Slots verstehen

Vue implementiert eine API zur Verteilung von Inhalten, die sich am Entwurf der Web Components Spezifikation orientiert und das <slot>-Element als Verteiler für Inhalte verwendet. Mehr dazu hier.

In diesem Template werden Slots an verschiedenen Komponenten, wie Elementen, Sections, Selections oder Containern verwendet. Slots ermöglichen einfach gesagt, einen einfachen Weg Content in die gewünschte Form zu portieren, indem die Logik und Styles der portierten Komponenten in der Slot-Komponente beschrieben wird.

Damit die Struktur besser verstanden wird, hier ein kleines Schaubild:

<Footer>
  <Component /> <- Slot wird gefüllt
</Footer>

Props verstehen

In Vue kann man neben Slots auch Props verwenden, um Informationen/Daten von einer übergeordneten Komponente an untergeordnete Komponenten weiterzugeben. Anders als bei Slots ist werden die Daten an festgelegte Stellen verteilt und nicht in beliebiger Reihenfolge. Mehr dazu hier.

Damit die Struktur besser verstanden wird, hier ein kleines Schaubild:

<Footer key="value" /> <- Property wird gefüllt

Dynamic Routing verstehen

Nuxt kann Dynamic Routing! Das bedeutet, dass z.B. aus der Anfrage einer API dynamische Routen/Links generiert werden können. Man könnte z.B. auf einer /events Page alle Events abfragen. Wenn man auf ein Event klickt, soll der Besucher auf die jeweilige Event-Page geleitet werden.

Vorweg sei gesagt, dass es zwei Möglichkeiten gibt dynamische Routen zu erzeugen.

Möchte man dynamische Seiten auf höchster Ebene wie www.leaf.io/ziel-seite erzeugen:

index.vue
_slug/
--index.vue

Von der ausgehenden Seite kann nur mit <NuxtLink to="/ziel-seite"> navigiert werden.

Möchte man dynamische Seiten hinter Pfaden wie www.leaf.io/event/ziel-seiteerzeugen:

events.vue
event/
--_slug.vue

Von der ausgehenden Seite kann mit <NuxtLink to="/event/ziel-seite"> navigiert werden.

Durch das $route Objekt kann programmatisch auf den Pfad zugegriffen werden, womit dann der individuelle Call für die dynamisch zu erstellende Seite gemacht werden kann.

In-Depth Informationen zu dynamischen Pages hierzu hier. Mehr Information zu der Erstellung dynamischer Pages anhand von API Calls hier


Netlify Functions verstehen

Netlify Functions geben die Möglichkeit Backend Code auszuführen, ohne einen Server nutzen zu müssen. Dies ist vor allem extrem Hilfreich, wenn man API Keys verstecken muss.

Mehr Werbung zu Netlify Functions hier.
Mehr Dokumentation zu Netlify Functions hier.
Mehr Info zum Verstecken von API Keys hier.


Contentful verstehen

Contentful ist ein Headless CMS, welches erlaubt, eigene Content Types zu definieren. Die individuell erstellbaren Felder der jeweiligen Content Types werden dann in der API ausgeliefert. Contentful ermöglicht es sozusagen die absolute Personalisierung einer Content API. Ein weiterer Vorteil gegenüber klassischen CMS ist, dass der Content über API an mehrere Ziele ausgeliefert werden kann bzw. in verschiedenen Projekten auf die selben Daten zugegriffen werden kann. So kann man den Content nicht nur auf der Website, sondern auch den selben Content auf einer App anzeigen.


Tailwind verstehen

Tailwind ist ein utility-first CSS Framework, welches einfaches und schnelles setzen von Stylings ermöglicht. In diesem Template wird Tailwind "Nested" genutzt, was bedeutet, dass die Tailwind nicht in das class Atribut gepackt wird, sondern in die CSS Referenzen im style Tag. Dies ermöglicht es den HTML Tags, wie gewöhnlich, Klassen zu geben und die Eigentschaften mit Tailwind zu beschreiben:

<style lang="postcss" scoped>
.footerWrapper {
  @apply flex flex-col-reverse items-center lg:items-start lg:flex-row;
  @apply py-9 lg:py-16;
}
</style>

Im Root Verzeichnis liegt eine Datei namens tailwind.config. Diese kann dazu genutzt werden um zusätzliche Definitionen hinzuzufügen oder verhandene zu überschreiben.

Die offizielle Dokumentation findet man hier.

Nuxt bietet in der Version des Templates eine Tailwind Vorschau an. Nachdem man das Projekt gestartet hat, kann man diese URL aufrufen, um die verschiedenen Tailwind-Klassen zu inspizieren unter http://localhost:3000/_tailwind/.



Components

Components sind vorgefertigte Website Elemente, werden durch Slots und Properties gefüllt. Sie bestehen wie vorher beschrieben aus verschiedenen Typen wie beispielsweise Elements oder Selections. Diese sind unter dem Ordner ~/components/ auffindbar.


Container

Container sind die tiefsten Komponenten. Container sind DIVs die spezielle Eigenschaften in Bezug auf die Positionierung auf Seiten haben. Ein Container kann beispielsweise dafür Sorgen, dass der gesamte Page Inhalt nicht den äußeren Window Rand berührt. Sie bilden oft die Root-Komponenten von Sections.


FluidContainer

Der FluidContainer ist ein Container, der sich über 100% der Window Width erstreckt, alles mittig und in einer Column verläuft.


BorderContainer

Der BorderContainer kümmert sich darum, dass Page Inhalt nicht breiter als ein gewisser Wert wird und handlet dabei diverse Bildschirmgrößen.



Sections

Sections sind nach den Containern die nächsthöheren Komponenten. Sections halten via Slots Elements und handlen die Abstände und Placements, also die Darstellung, dieser Children-Elements sinngemäß nach dem Gesetz der Kontinuität im Webdesign.


ContentSection

ContentSections sind dafür gedacht den Seiteninhalt/Content sinngemäß voneinander zu trennen. So sollte zum Beispiel in einer ContentSection das Team vorgestellt werden und in einer anderen die Vorgehensweise des Unternehmens.

Konkret bestimmt die ContentSection ein festgelegtes Styling in Bezug auf Margins und Positionierung der Items. Mit der Property styles kann der Section eine Hintergrund-Farbe gegeben werden. Sie kümmert sich also um die räumliche Trennung von Inhalten nach dem Prinzip des Gesetzes der Nähe.

<ContentSection styles="bg-subliminal">
  {{ slots }}
</ContentSection>

SwipeSection

Die SwipeSection nimmt durch Slots Komponenten auf. Diese können kann horizontal geswiped werden. Diese Section eignet sich hervorragend für die dynamische Erzeugung von Komponenten via API-Abfragen mit einer For-Schleife.

Da eine solche Section erfahrungsgemäß eine Set an Themen-bezogenen Daten zeigt, kann man der SwipeSection durch Props title und text Überschrift und Unterüberschrift mitgeben. Da es sich hier um eine andere Form einer ContentSection handelt, sind die Margins gleich der ContentSection und die Hintergrundfarbe kann ebenfalls über die styles Property angepasst werden. Die Margins des ersten und letzen Child innerhalb der SwipeSection anhand des Stylings des BorderContainer berechnet.

<SwipeSection
      styles="bg-subliminal"
      title="Nike Schuhe"
      text="Sichere dir deine Lieblingsschuhe!"
    >
      <div v-for="object in objects" :key="object.id">
        <ProductCard
          :img="require('~/assets/images/heroes/' + object.img + '.jpg')"
          :title="object.title"
          description="object.description"
          slug="object.slug"
        />
      </div>
</SwipeSection>

SmallCenteredSection

Eine SmallCenteredSection positioniert sich mit einer engen maximalbreite in der Mitte des Bildschirms. Dieser Effekt ist nur auf dem Desktop sichtbar, auf dem Handy passt sie sich der Bildschirmbreite an.


LegalSection

Die LegalSection ist dazu da, um Fließtexte wie Datenschutzerklärungen und Impressum abzubilden. Die Breakpoints sind so gesetzt, dass sie sich von der absolut positionierten NavBar absetzen.


TextSection

Die TextSection zeigt die Items und Text linksbündig an.


HideOn

Die HideOn Section versteckt oder zeigt den Inhalt ab dem Moment, an dem die NavBar den Drawer zugänglich macht. Sie nimmt über die device Property die Werte "mobile" oder "desktop" entgegen und kann dann konditionell Elemente je nach Bildschirm Typ anzeigen/verstecken:

<HideOn device="mobile">
  <SocialIconSelection
    :facebook="facebook"
    :instagram="instagram"
    :tiktok="tiktok"
    :twitter="twitter"
    :reddit="reddit"
    :snapchat="snapchat"
    :linkedin="linkedin"
    :youtube="youtube"
  />
</HideOn>

WrapSection

Die WrapSection ist eine Section für Items, die bei maximal erreichter Breite in eine neue "Zeile" umbrechen sollen. Dies eignet sich beispielsweise für Team Profil Bilder, da hier einfach das gesamte Team reingeworfen werden kann.


Elements

Elements sind vorgefertigte UI-Elemente und bilden mit Selections die höchste Ebene von Komponenten. Elements sind Website Elemente die im User Interface zu sehen sind.


YouTubePlayer

Der YouTube Player ist immer im Aspect Ration von 16:9 und füllt die gesamte Breite einer Section. Um ein Video zu targetieren muss in der Propterty id die Video ID mitgegeben werden.

<YouTubePlayer id="Xh11iUpJs4Y" />

WideHeader

Ein WideHeader ist ein Hero Image, das sich über die gesamte Bildschirmbreite zieht. Die Komponente erwartet eine Property names img, der eine bg Tailwind-Klasse übergeben werden muss. Diese wird dann als Background Image eingesetzt.

<WideHeader img="bg-hero"/>

ProfileCard

Eine ProfileCard beinhaltet ein Bild, einen Namen, eine Position und ein Link bzw. Slug, mit dem der Pagebesucher auf ein weiterführendes Profil oder eine LinkedIn Seite geführt werden kann.

<ProfileCard
  :img="require('~/assets/images/heroes/benny.jpg')"
  name="Benedikt Götemann"
  position="Mensch-Mensch"
  slug="/benny"
/>
<ProfileCard
  :img="require('~/assets/images/heroes/benny.jpg')"
  name="Benedikt Götemann"
  position="Mensch-Mensch"
  link="https://www.linkedin.com/benediktgoetemann"
/>

ProductCard

Eine ProductCard beinhaltet in der Grundversion ein Bild, einen Titel, eine Beschreibung und Slug, mit dem der Pagebesucher auf eine weiterführendes Page geführt werden kann. Diese Komponente muss immer an den Kontext angepasst werden.

<ProductCard
  :img="require('~/assets/images/heroes/hero.jpg')"
  title="object.title.value"
  description="Lorem ipsum dolor sit amet."
  slug="yolo"
/>

Divider

Ein Divider ist ein Element, das eine Trennlinie über 100% der Parent Komponente erzeugt.


Button

Der Button kann eine Route oder einen externen Link erzeugen. Dies legt man fest, indem man entweder die Properties link oder slug nutzt:

<Button slug="/registrieren" btnText="Jetzt registrieren" />
<Button link="https://www.apple.com" btnText="Apple Website" />


Navigation


NavBar

Die Navbar ist eine responsive Navigationsleiste, die einen Drawer erzeugt, wenn man auf Bildschirmen in Handygröße auf die Seite zugreift. Sie beinhaltet NavBarItems und Icons, die zu Social Media Plattformen führen.

<NavBar
  absolute="true" //optional
  :pages="['Home', 'About', 'Blog']"
  callToAction="Login"
  facebook="apple"
  instagram="apple"
  soundcloud="apple"
  tiktok="apple"
  youtube="apple"
/>

Automatisches Erstellen von Seiten

Die Navigationsleiste kann dynamisch mit Seiten gefüllt werden, indem man die :pages="[...]" Property mit Strings füllt. Im Hintergrund werden automatisch Routes zu den jeweiligen Seiten erstellt. Diese Seiten müssen dann nur noch unter dem ~/pages/ angelegt werden. Aus :pages="['Home', 'Jetzt registrieren']" werden die Routes domain.com/home und domain.com/jetzt-registrieren erstellt.


Automatisches Erstellen von Social Media Icons

Die Navigationsleiste kann Social Media Icons gefüllt werden, wobei die Social Media Icons nur im Drawer in der Mobile Ansicht zu sehen sind. Dieses Template unterstützt dabei Facebook, Instagram, TikTok, YouTube, Reddit, Twitter, LinkedIn, SoundCloud und Snapchat - diese können aktiviert werden, indem man die jeweilge Property erzeugt. Die Props der Social Media Icons erwarten keinen vollständigen Link sondern lediglich der Path. Der richtige Link wird in der SocialIconSelection-Komponente erzeugt.


Automatisches Erstellen eines Primary Button

Darüber hinaus kann durch die Property callToAction ein Button erzeugt werden. Dieser generiert ebenfalls automatisch eine Route zur jeweiligen Page.


Absolute Mode

Zuguterletzt kann über die Prop absolute entschieden werden, ob die Navigationsleiste im Desktop State über dem Header "floaten" soll, oder einen eigenen abgetrennten Bereich bekommt, indem man absolute auf true setzt.


⚠️

Wenn die NavBar nicht absolut ist, muss in der [LegalSection](#legalsection) das CSS angepasst werden, da die Margins nicht mehr passen.


Footer

Der Footer kann wie die Navigationsleiste mit Seiten und Social Media Icons gefüllt werden, wobei die Social Media Icons nur auf dem Desktop angezeigt werden.

Pages werden über Slots mithilfe von LinkSelections eingefügt, die wiederum eine Prop :pages="['page']" beinhaltet, die genau wie bei der NavBar einfache Strings in Paths verwandelt.

Der Copyright String wird mithilfe der Property company gefüllt und aktualisiert das Jahr automatisch.

<Footer
  company="Leaf GmbH"
  facebook="leaf"
  instagram="leaf"
  tiktok="leaf"
  linkedin="leaf"
  youtube="leaf"
>
  <LinkSelection
    title="Legal"
    :pages="['Impressum', 'Datenschutz', 'AGB']"
  />
  <LinkSelection
    title="About"
    :pages="['Vision', 'Team', 'Roadmap']"
  />
</Footer>



Selections

Selections sind die höchste Ebene von Komponenten. Sie bilden Ansammlungen von Komponenten, die durch Props mit Daten gefüllt werden und festgelegte Dimensionen und Behaviors haben, da sie sich an jedem Ort gleich verhalten sollen


LinkSelection

Eine LinkSelection erzeugt eine Ansammlung von Links mit einer Überschrift und werden unter anderem in einem Footer eingesetzt.

<LinkSelection
  title="Legal"
  :pages="['Impressum', 'Datenschutz', 'AGB']"
/>

SocialMediaSelection

Eine Social Media Selection beinhaltet alle möglichen Social Media Plattformen. Wenn die Prop einer Plattform ausgefüllt wird, erzeugt sie das jeweilige Icon dazu.

<SocialIconSelection
  :facebook="facebook"
  :instagram="instagram"
  :tiktok="tiktok"
  :twitter="twitter"
  :reddit="reddit"
  :snapchat="snapchat"
  :linkedin="linkedin"
  :youtube="youtube"
/>

PrivacyTextSelection

Die PrivacyTextSelection beinhaltet alle Paragraphen, die für eine Datenschutzerklärung notwendig sind. Durch Props können die Gesetzestexte für Referenzierungspflichtige Technologien aktiviert werden und Unternehmensdaten eingesetzt werden. Hierbei setzt sich die Numerierung der Abschnitte automatisch. In dieser Auflistung sind alle möglichen Properties zu sehen:

Coming soon...


Figures

Figures sind Komponenten die ein Bild repräsentieren, wie beispielsweise das Logo und ein Icon



Flow

Flows sind dynamische Wizards/Kontaktformulare, welche durch ein externes JSON Dokument beschrieben werden können. Sie bestehen aus 4 Komponenten und einer Section, die den Flow in der Mitte des Screens platziert.


Implementierung

Ein Flow wird durch eine FlowMachine erzeugt. Sie nimmt das JSON Dokument mit der optionData Property entgegen, welches durch die asyncData Hook aus dem Static Ordner gezogen wird. Je nach Inhalt des JSON Dokuments, kann die FlowMachine entweder Option- oder Formular-Komponenten erzeugen. Wenn der Flow den gesamten Page Inhalt darstellen soll, kann er in eine FullScreenFlowSection gepackt werden.

  <FullScreenFlowSection>
      <FlowMachine :optionData="contact"/>
  </FullScreenFlowSection>
<script>
import contact from "~/static/flows/contact.json";

export default {
  async asyncData() {
    return { contact };
  },
}
</script>

Flow-Design

Die FlowMachine kann Options und Formulare kreieren. Im JSON Dokument muss lediglich ein bestimmtes Schema beachtet werden.

Zu allererst müssen Typ, Title, Key des jeweiligen Schritts im Flow definiert werden.

[{
        "key": "target-group", // Key ermöglicht Navigation zu dieser Frage. Benennung frei.
        "type": "option", // oder "form". Erzeugt entweder Option oder Form.
        "subtext": "Los gehts!", // Dynamischer Sub-Titel
        "title": "Zu welcher Gruppe gehören Sie?", // Dynamischer Titel
        "callto": "Käufer oder Verkäufer?",  // Dynamischer Titel für alternatives Design
        "fields": [ // Fields sind die Options oder Inputs die je nach type gerendert werden sollen.
            {
               ...
            },
            ...
        ]
    },
    ...
]

Danach müssen je nach Type (Form oder Option) die Inputs oder Options definiert werden.

[{
        ...
        "type": "option", // Bei Option-Type.
        ...
        "fields": [{
            {
              "type": "relationalPath", // Einstellung für Navigation zu einer anderen Option.
              "value": "Antwort 1", // Text der gezeigt wird
              "icon": "platforms/gp.svg", // Pfad zum Icon ab dem assets-Ordner.
              "path": "reason-organizer", // Key der Option die bei Klick angezeigt werden soll.
              "event": "isOrganizer" // Ermöglicht Facebook Pixel Events zu feuern.
            },
            {
              "type": "internalPath", // Navigiert zu einer anderen Seite der Website
              "value": "Mobile-Privacy", 
              "icon": "privacy/mobile-button-solid.svg",
              "path": "/mobile-privacy" // Slug der Seite die nach Klick angezeigt werden soll.
              "event": "isOrganizer"
            },
            {
              "type": "externalPath", // Navigiert zu einer externen URL
              "value": "Antwort 3",
              "icon": "platforms/gp.svg",
              "path": "https://www.google.com", // URL die nach Klick geladen werden soll..
              "event": "isOrganizer"
            }
        ]
    },
    ...
]
[{
        ...
        "type": "form", // Bei Option-Type.
        ...
         "fields": [{
            {
                "type": "text", // oder "email", "datetime-local", "date", "textarea"
                "name": "Label", // Label / Name
                "required": true // oder false
            },
        ]
    },
    ...
]

Mit jedem Formular wird ein Submit-Button erzeugt. Wenn das Formular abgeschickt wurde, wird der User zu einer Success Page Navigiert, die folgendermaßen konfiguriert werden kann:

    {
        "key": "success-page",
        "type": "success",
        "subtext": "Los gehts!",
        "title": "Formular erfolgreich versendet!",
        "callto": "Formular erfolgreich versendet!",
        "img": "images/heroes/hero.webp" // Pfad zum Image ab dem assets-Ordner.
    }

Validation

Das Forumular validiert sich automatisch und stellt ein Objekt bereit, welches dann via API verschickt werden kann.

Extras

Extras sind externe Tools, die nicht über API eingebunden werden. Bspw. Module, Script Tags mit diversen Funktionen


AXIOS

AXIOS macht es ein wenig leichter APIs anzusteuern. Dies kann man entweder innerhalb von Komponenten via Method oder Hooks machen oder (nur auf Seitenebene machbar) mit der AsyncData-Function. Bindet man einen Request in einer AsyncData Methode ein, so wird der Request während Build Time ausgeführt. Das heißt, dass der Request nicht bei jedem Laden der Seite durch Pagebesucher neu gemacht wird, sondern bei Build Time in die statische Seite "gebrannt" wird.

<template>
  <FetchButton :built="built" />
</template>

<script>
export default {
  async asyncData({ $axios }) {
    const built = await $axios.$get("https://swapi.dev/api/people/1/");

    return { built };
  },
}
</script>

Auf Komponentenebene ist die AsyncData Methode nicht erreichbar. Stattdessen nutzt man async Methods, die getriggert werden müssen oder fetcht während den Hooks. Dies ermöglicht dynamisches Laden von Content:

<template>
  <div>
    <!-- Request wird mit Klick durch Methode getriggert -->
    <div @click="fetchSomething()">Clicked: {{clicked.name}}</div>
    <!-- Request wird in einer Hook-Methode getriggert -->
    <div>Fetched: {{ fetched.mass }}</div>
    <!-- Request wird beim Laden der Parent Page getriggert und via Prop in die Komponente gebracht -->
    <div>Build Time: {{ built.eye_color }}</div>
  </div>
</template>

<script>
export default {
  props: ["built"],
  data() {
    return {
      clicked: [],
      fetched: [],
    };
  },
  methods: {
    async fetchSomething() {
      this.clicked = await this.$axios.$get("https://swapi.dev/api/people/1/");
    },
  },
  async fetch() {
    this.fetched = await this.$axios.$get("https://swapi.dev/api/people/1/");
  },
};
</script>

⚠️

Wenn man die AXIOS-Method innerhalb außerhalb einer AsyncData, in jener man einen Parameter mitgibt (```async asyncData({ $axios })```), also innerhalb einer Methode oder Hook, verwenden möchte, muss man sie mit ```this``` referenzieren. ```js let res = await this.$axios.$get("https://swapi.dev/api/people/1/"); ```


In diesem Template wird Axios auch in den Netlify-Functions genutzt. Hier bindet man den Request folgendermaßen ein:

const axios = require('axios')

const handler = async (event) => {
  const url = 'https://api.propstack.de/v1/units?with_meta=1&expand=1&api_key=' + process.env.API_SECRET
  try {
    const { data } = await axios.get(url)

    return {
      statusCode: 200,
      body: JSON.stringify(data)
    }
  } catch (error) {
    const {
      status, statusText, headers, data } = error.response
    return {
      statusCode: status,
      body: JSON.stringify({ status, statusText, headers, data })
    }
  }
}

module.exports = { handler }

⚠️

Man kann Nuxt/Axios in der nuxt.config eine BaseURL hinzufügen. In diesem Fall ist Axios darauf ausgelegt keinen ganzen Link, sondern lediglich die relevanten Pfade anzunehmen. Dann sollte die URL nicht ```https://swapi.dev/api/people/1/``` sondern ```/api/people/1/``` lauten.

Für weitere Infos zur Benutzung in Nuxt muss man hier nachlesen. Für weitere Infos zur Benutzung innerhalb von Netlify Functions muss man hier nachlesen.


GSAP

Für die Benutzung innerhalb von Nuxt muss man hier nachlesen und für Dokumentation der generellen Funktionalität hier nachlesen.


Heyflow

Um Heyflow einzubinden bedarf es zwei Schritten. Zuerst erstellt man im Template Tag ein DIV und gibt ihr eine ID mit einem beliebigen Namen. Zuguterletzt ruft man die Heyflow-Methode in der Mounted-Methode im Script-Tag auf. Die Heyflow-Methode erwartet zwei Parameter: flowId und divId.

<template>
  [...]
    <div id="heyflow"></div>
  [...]
</template>
<script>

  export default Vue.extend({
    [...]
      mounted() {
        heyflow('djefj23f', 'heyflow')
      },
    [...]
  })

</script>

Facebook Pixel

Für die Konfiguration des Facebook Pixels bitte hier nachlesen.


Meta Tags in Nuxt

In Nuxt ist es möglich Meta Tags zur SEO zu verwenden. Die Mechanik von Nuxt Meta Tags basiert auf dem Vue Meta Package. Die Dokumentation kann als Referenz genutzt werden, wobei es dabei ein paar Unterschiede im Naming gibt. Mehr dazu in Videoform hier.


Cookie Control

Diesem Template ist Nuxt-Cookie-Control eingebunden. Für die Konfiguration dieses Packages bitte hier nachlesen.


Dark/Light Mode

Für die Konfiguration des Dark/Light Modes bitte hier nachlesen und/oder dieses Beispiel anschauen.



Netlify Functions

Die Netlify Functions werden genutzt um Anwendungen zu schreiben, bei denen API Key versteckt werden sollen. Innerhalb einer Function kann ein Teil einer API abstrahiert werden, um den Client nur die Information zur Verfügung zu stellen, die der Client braucht

Wenn mit Netlify Functions entwickelt wird, muss yarn netlify dev anstelle von yarn dev verwendet werden - andererseits funktionieren die Functions nicht.


Neue Function erstellen

Man erstellt eine neue Function mit diesem Command:

yarn netlify function:create --name example


Die Hello World Function mit Comments

const handler = async (event) => {
  try {
    // event.queryStringParameters greift auf die Parameter im Query String zu
    const subject = event.queryStringParameters.name || 'World' // Defaultet auf 'World'
    return {
      statusCode: 200,

      // RESPONSE BODY, der aus dem Request generiert und dem Client gegeben werden kann
      body: JSON.stringify({ message: `Hello ${subject}` }), 
      
      // // more keys you can return:
      // headers: { "headerName": "headerValue", ... },
      // isBase64Encoded: true,
    }
  } catch (error) {
    return { statusCode: 500, body: error.toString() }
  }
}

module.exports = { handler }

Eine tatsächliche Function

const axios = require('axios')

const handler = async (event) => {
  const url = 'https://api.propstack.de/v1/units?with_meta=1&expand=1&status=15503&api_key=' + process.env.API_SECRET
  try {
    const { data } = await axios.get(url)

    return {
      statusCode: 200,
      body: JSON.stringify(data.data)
    }
  } catch (error) {
    const {
      status, statusText, headers, data } = error.response
    return {
      statusCode: status,
      body: JSON.stringify({ status, statusText, headers, data })
    }
  }
}

module.exports = { handler }

Weitere Infos dazu hier.



Eine Netlify Function fetchen

Netlify Functions erstellen einen Endpunkt auf der Domain der Page:

https://www.leaf.com/.netlify/functions/myFunction.js

So können dann die Functions aufgerufen werden:

[...] = await this.$http.$get('/.netlify/functions/myFunction?parameter="value"');

⚠️

Damit die Function aufgerufen werden kann, muss in der ```nuxt.config``` unter den Axios Options die Base URL vergeben werden. Diese Requests sind in Verbindung mit Netlify NICHT dafür gedacht, Daten aus anderen Quellen zu fetchen. Mithilfe der Axios Proxys sind jedoch weitere Konfigurationen möglich. Dazu bitte [hier nachlesen](https://axios.nuxtjs.org/options).


Netlify Functions für Production konfigurieren

Damit Netlify Functions sowohl lokal, als auch in Production funktionieren, muss zuallererst in der nuxt.config ganz oben eine Zeile hinzugefügt werden, die im Node Environment überprüft, ob die gebuildete Seite sich in der Development- oder Production-Umgebung befindet:

let development = process.env.NODE_ENV !== 'production'

Als nächstes muss der baseURL mithilfe der development Variable beibringen, je nach Umgebung eine andere BaseURL zu nutzen.

axios: {
  baseURL: development ? 'http://localhost:8888' : 'https://bg-template.netlify.app', 
},

⚠️

Wenn man neu hinzugefügte Netlify Functions innerhalb von AsyncData Methods verwendet, gibt es beim Deployment Fehler, da die Function zur Build Time noch nicht von der Production URL erreicht werden kann. Beim nächsten Deployment funktioniert alles ohne Probleme.




Contentful

Contentful ist ein Headless CMS, welches dem User erlaubt, eigene Content Models zu erstellen. Meine Lösung habe ich aus diesen Artikeln und diesem Artikel zusammengesetzt, wobei mich dieser Artikel auf den richtigen Pfad gebracht hat.


Dynamic Website Content mit Contentful

Im Gegensatz zu allen Tutorials, sollte dieses Template die Daten nicht über die asyncData Methode sondern mit der fetch Hook laden, da asyncData nur auf Page-Ebene (nicht in Komponenten) möglich ist und die Daten zur Build-Time, also Server-Side, lädt, wodurch neu hinzugefügte Posts in Contentful ohne einen Rebuild auf der Seite nicht angezeigt würden.

Leider führt dieser Ansatz beim Page-Reload zu einem 404, da dynamische Routen behandelt werden wie eine SPA (Single Page Application). Der Client pusht die Seite zwar auf den Router und fetcht die Daten, aber der Server kennt die tatsächliche Datei nicht.

Demnach gilt: Wer dynamische Routen für Blogposts generiert, sollte auf Rebuilding setzen. ABER! Es gibt eine coole Möglichkeit eine Kombination aus static und dynamic herzustellen

Die fetch Hook lädt by default nicht Client-Side. Man kann jedoch außerhalb der fetch Hook eine Zeile Code hinzufügen, die der Page oder Komponente erlaubt Client-Side die Daten zu laden:

  async fetch() {
    try {
      if (!contentfulClient) return;
      const e = await contentfulClient.getEntries({
        content_type: "event",
        include: 10,
      });
      const p = await contentfulClient.getEntries({
        content_type: "blogPost",
        include: 10,
      });
      if (e.items.length > 0 && p.items.length > 0) {
        this.events = e.items;
        this.posts = p.items;
        //console.log(response);
      }
    } catch (err) {
      console.error(err);
    }
  },
fetchOnServer: false,

⚠️

In diesen Beispiel werden mehrere Content Types auf einer Seite geladen. Bei nur einem Type muss der Request entsprechend angepasst werden.


Wenn man das nicht macht, werden die statischen Seiten generiert. Wenn man es macht, hat man das Problem mit den fehlenden Files aufgrund der dynamischen Routen. Nuxt bietet die Möglichkeit beim builden alle Files aus den dynamischen Routen zu erstellen, indem man die aus der Contentful API generierten Routen auswertet. Dies ist nicht nur für Contentful wichtig, sondern für alle dynamisch generierten Routes. Deshalb ist es nochmal unter Deployment bei Dynamische Pages statisch machen beschrieben. Die genaue Funktion die für Contentful nötig ist in der nuxt.config:

generate: {
  routes: function () {
    return contentfulClient.getEntries({
        content_type: "blogPost",
        include: 10,
      })
      .then((response) => {
        return response.items.map((post) => {
          console.log(post.fields.slug)
          return post.fields.slug
        })
      })
  }
}

Möchten wir mehrere dynamische Routen von Contentful statisch machen, muss der Code folgendermaßen aussehen:

generate: {
  routes: function () {
    return Promise.all([
        contentfulClient.getEntries({
          content_type: "blogPost",
          include: 10,
        }),
        contentfulClient.getEntries({
          content_type: "event",
          include: 10,
        })
      ])
      .then(([blogEntries, eventEntries]) => {
        return [
          ...blogEntries.items.map(entry => `/post/${entry.fields.slug}`),
          ...eventEntries.items.map(entry => `/event/${entry.fields.slug}`),
        ]
      })
  }
}

Wenn wir das machen, können wir die Daten auf den dynamisch generierten Seiten vom Client fetchen. Der Blog User hat somit das Gefühl, dass seine neuen Blog Posts sofort auf der Seite erscheinen. Im Hintergrund triggert Contentful jedoch eine Webhook, die Netlify signalisiert die Seite neu zu builden. In diesem Prozess werden dann alle neuen nötigen Pages erstellt. Wie man die Webhook einstellt, wird unter Automatische Webhook Builds mit Contentful und Netlify erkärt.


Rich Text Processing

Contentful erlaubt innerhalb vom eigenen Rich Text Editor Verlinkungen zu anderen Contentful Entries zu machen. Dadurch ist es möglich einen Bereich komplett dynamisch zu rendern. Nach verschiedenen Anläufen mit bestehenden Packages, habe ich jedoch beschlossen eine eigene Komponente zu schreiben. Die CFRichText Komponente nimmt mit der body Property das Contentful Rich Text Objekt an und verarbeitet es dann.

<CFRichText :body="body" />

Damit das funktioniert, muss man die Einsichtstiefe in den Contentful Client konfigurieren, ansonsten klickt man sich durch zwei Ebenen der Response und sieht danach die Daten nicht mehr. Die include: 10 Konfiguration erlaubt tiefere Einsicht von insgesamt 10 Ebenen und zeigt somit den kompletten Inhalt von Embeded Entries im Rich Text Field.

async fetch() {
    try {
      if (!contentfulClient) return;
      let response = await contentfulClient.getEntries({
        content_type: "blogPost",
        include: 10, // mehr tiefe in response
        "fields.slug[in]": this.$route.params.slug, 
      });
      if (response.items.length > 0) {
        this.fields = response.items[0].fields;
        this.body = response.items[0].fields.body.content; // rich text object
      }
    } catch (err) {
      console.error(err);
    }
  },

Content mit Query Parametern filter

Contentful bietet die Möglichkeit Content direkt nach gewissen Parametern zu filtern. Wenn man beispielsweise nur Content mit einem gewissen Tag fetchen möchte kann man das folgendermaßen machen:

const response = await contentfulClient.getEntries({
  content_type: "event",
  include: 10,
  'metadata.tags.sys.id[in]': 'techno'
});

Mehr Informationen zu den Query Parametern gibt es hier


Dynamische Hauptseiten

Im Gegensatz zu der Variante dynamischen Routen hinter einem Pfad zu erzeugen, wie bei diesem File Baum...:

posts.vue
post/
--_slug.vue

... ist es auch möglich dynamische Pages auf erster Pfadebene zu erzeugen, für beispielsweise alle Hauptseiten. Dies ist durch diesen Filebaum möglich:

index.vue
_slug/
--index.vue

Erstellt man dynamische Routen auf diese Weise werden keine zusätzlichen Pfad-Ebenen erzeugt. Das heißt, dass die Pfade der daraus erstellten dynamischen Pages folgendermaßen aussehen...:

www.leaf.com/page

... und niemals so aussehen können:

www.leaf.com/posts/page

Mit ein paar Konfigurationen im Rich Text Processing können in Rich Text Editoren auch komplette Haupt Pages gestaltet werden.




Depoloyment


Nuxt und Netlify Trailing Slash Problem

Wenn die Nuxt App auf Netlify deployed wurde und ein User auf einer Seite einen Page-Refresh durchführt, redirected Netlify den User zur URL mit einem angehängten Slash, also von leaf.io/blog zu leaf.io/blog/. Dies führt zu Problemen. Um diesen Umstand zu fixen kann man so wie hier beschrieben, die Einstellungen in den Deploy Settings des Netlify Projekts anpassen.


Dynamische Pages statisch machen

Wenn man aus API Calls Pages automatisch generiert, muss man dafür sorgen, alle Pages beim Build zu generieren. In Verbindung mit der fetchOnServer: false Konfiguration in den Abfragen auf Seiten dynamischer Routen werden Inhalte nicht statisch generiert, sondern nur die jeweiligen Datein und somit die Pfade - die Inhalte laden Client-Side. Generiert man die Seiten nicht auf diese Weise, erhält der User bei einem Page Refresh eine 404-Error (Not Found).

Mehr dazu hier.

generate: {
  routes: function () {
    return axios.get('https://my-api/users')
      .then((res) => {
        return res.data.map((user) => {
          return '/users/' + user.id
        })
      })
  }
}

Automatische Webhook Builds mit Contentful und Netlify

Wie das automatisierte Builds funktionieren wird hier erklärt.


PageSpeed Insights Optimierung

Hier ein paar Informationen zum Deployment, um den Page Speed zu erhöhen. Um den Page Speed zu testen, lohnt es sich die Website auf Google's PageSpeed Insights zu testen.

  1. Bilder im WebP Format!
  2. Bilder im richtig dimensionieren!
  3. Fonts nach Development herunterladen und einpflegen!

SEO Verbesserungen:

Damit die Seite so SEO-optimiert wie möglich ist, müssen diese Schritte ausgeführt werden:

  1. Apple Touch Icon erstellen

... more to come

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published