In dieser Artikelserie werde ich demonstrieren, wie man eine Website mit Optimizely, Next.js und Vercel ausliefert. In diesem ersten Teil konzentriere ich mich auf die Erstellung eines einfachen POC, in dem ich eine vereinfachte Version der AlloyTech-Website reproduziere, die jedoch mit Next.js und Vercel statisch generiert wird.
Lösungsarchitektur
Optimizely wird verwendet, um den Inhalt zu verwalten, wobei alle Inhaltsaktualisierungen mit dem Inhaltsdiagramm synchronisiert werden.
Die Präsentationsschicht wird mit Next.js, einem React-Framework, entwickelt. Next.js kann entweder zur Erstellungszeit eine statische Website generieren, das serverseitige Rendering verarbeiten oder eine Kombination aus beidem.
Vercel ist ein globales Netzwerk, das Hosting anbietet. Der Inhalt wird weltweit zwischengespeichert.
Schritt 1 – Verwalten des Inhalts
Der erste Schritt ist einfach und nach Abschluss erhalten Sie eine Instanz von AlloyTech, deren Inhalt mit dem Inhaltsdiagramm synchronisiert ist.
Installieren Sie AlloyTech
Installieren, erstellen und führen Sie die Demo-Site mit den folgenden Befehlen aus. Wenn Sie die Lösung zum ersten Mal ausführen, werden Sie aufgefordert, ein Administratorkonto zu erstellen. Sobald dies abgeschlossen ist, haben Sie eine Website, die Sie zum Testen verwenden können.
dotnet new epi-alloy-mvc
dotnet build
dotnet run
Content Graph installieren
Führen Sie die folgenden Schritte aus, um Content Graph auf der AlloyTech-Demo-Site hinzuzufügen und zu konfigurieren. Sie müssen sich an Optimizely wenden, um Zugriff auf einen AppKey zu erhalten.
dotnet add package Optimizely.ContentGraph.Cms
"Optimizely": {
"ContentGraph": {
"GatewayAddress": "https://cg.optimizely.com",
"AppKey": "",
"Secret": "",
"SingleKey": "",
"AllowSendingLog": "true"
}
}
Nach der Installation und Konfiguration können Sie Ihre Inhalte mithilfe des geplanten Jobs „Content Graph Content Synchronization Job“ mit dem Content Graph synchronisieren. Inhalte werden auch synchronisiert, wenn sie veröffentlicht werden.
Schritt 2 – Entwickeln Sie die Website mit Next.js
Das Erstellen einer neuen Website mit Next.js ist sehr einfach, aber werfen Sie einen Blick auf https://nextjs.org/docs/getting-started für detailliertere Anweisungen.
Sie können nun auf die Site zugreifen, indem Sie in Ihrem Browser „http://localhost:3000/“ eingeben.
Next.js enthält keine GraphQL-Bibliotheken, ich habe dafür das ApolloClient-Paket hinzugefügt.
npm install @apollo/client graphql
Neuerstellung der Homepage
Ich möchte eine vereinfachte Version der AlloyTech-Homepage erstellen. Ich werde die Primärnavigation zusammen mit den Blöcken aus dem Hauptinhaltsbereich rendern, dies ist analog zur Vorgehensweise in der C#-Version der Seite.
Die Seite hat keine Kenntnis darüber, woher die Daten stammen, diese werden über einen weiteren Schritt übergeben. Es verwendet nur das Objekt, das in den ‘Props’ gesendet wird.
type PageProps = {
page: any;
navigation: any;
};
function Home(props: PageProps) {
const { page, navigation } = props;
return (
<>
{page.MetaTitle}
>
)
}
Jede Next.js-Seite kann ein „getStaticProps‘-Funktion, die während des Erstellungsprozesses verwendet wird, um die beim Rendern verwendeten Requisiten zurückzugeben. Hier fragen wir den Content Graph ab, um die Daten für die Startseite (und Navigation) zu erhalten.
Notiz: ‘getStaticProps’ wird nur für die statische Site-Generierung verwendet, für das serverseitige Rendering wird eine andere Methode aufgerufen.
export const getStaticProps: GetStaticProps = async (context) => {
const httpLink = new HttpLink({ uri: process.env.GRAPHQL_HOST });
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
ssrMode: true
});
var { data } = await client.query({
query: StartPageQuery
})
var startPage = data.StartPage.items[0];
var { data } = await client.query({
query: NavigationQuery
})
var navigation = data.StartPage.items[0];
console.log(navigation)
return {
props: {
page: startPage,
navigation: navigation
},
}
}
GraphQL-Abfrage zum Abrufen der Startseite.
import { gql } from '@apollo/client';
const StartPageQuery = gql`
query MyQuery {
StartPage(locale: en) {
items {
Name
TeaserText
RouteSegment
MetaTitle
MetaKeywords
MetaDescription
MainContentArea {
DisplayOption
Tag
ContentLink {
Id
Expanded {
Name
ContentType
... on JumbotronBlock {
Name
Heading
Image {
Url
}
ButtonText
ContentType
SubHeading
}
... on TeaserBlock {
_score
Name
Image {
Url
}
Heading
Text
}
}
}
}
}
}
}`
export default StartPageQuery
Produktseiten
Die Startseite ist ein einfaches Beispiel, aber was passiert, wenn Sie viele Inhalte haben, die dieselbe Vorlage verwenden? In AlloyTech gibt es 3 Produktseiten, auf die als untergeordnete Seiten der Homepage zugegriffen wird.
Routing
Die Namenskonvention von ‘[product-slug].tsx‘ bedeutet, dass die Seite eine dynamische Route ist. Der Name in den eckigen Klammern ‘[]‘ ist nicht wichtig.
Next.js geht hier näher darauf ein: https://nextjs.org/docs/routing/introduction.
Generieren der Routen
Ähnlich wie die ‘getStaticProps‘ Funktion, Next.js hat einen Ansatz zum Generieren der Routen, ‘getStaticPaths‘. Dies wird auch zur Build-Zeit aufgerufen.
export const getStaticPaths: GetStaticPaths = async () => {
const httpLink = new HttpLink({ uri: process.env.GRAPHQL_HOST });
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
ssrMode: true
});
var { data } = await client.query({
query: gql`query ProductPagesQuery {
ProductPage(locale: en) {
items {
Name
RouteSegment
}
}
}`
})
var pages = data.ProductPage.items;
const paths = pages.map((page: any) => ({
params: { slug: page.RouteSegment}, locale: 'en',
}));
return { paths, fallback: false };
};
Generieren der Seite
‘getStaticPaths’ für den Aufbau aller Routen verantwortlich ist, jede Route wird dann verwendet, um eine einzelne Seite zu generieren, wobei die Routendaten an ‘getStaticProps‘.
export const getStaticProps: GetStaticProps = async ({params}) => {
if (!params || !params.slug) {
return { props: {} };
}
const httpLink = new HttpLink({ uri: process.env.GRAPHQL_HOST });
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
ssrMode: true
});
var { data } = await client.query({
query: ProductPageQuery,
variables: {
segment: params.slug
}
})
var page = data.ProductPage.items[0];
var { data } = await client.query({
query: NavigationQuery
})
var navigation = data.StartPage.items[0];
return {
props: {
page: page,
navigation: navigation
},
}
}
Die folgende GraphQL-Abfrage ruft die spezifische Seite ab, die der Route entspricht
import { gql } from '@apollo/client';
const ProductPageQuery = gql`
query ProductPageQuery($segment: String) {
ProductPage(locale: en, where: {RouteSegment: {eq: $segment}}) {
items {
Name
MetaTitle
MetaKeywords
MetaDescription
MainBody
TeaserText
RelativePath
PageImage {
Url
}
RouteSegment
}
}
}
`
export default ProductPageQuery
Inhaltsbereiche / Blöcke
Für diesen POC habe ich meinen eigenen Content Area Render erstellt, da dies ein Optimizely-Konzept ist, das eine benutzerdefinierte Entwicklung innerhalb Ihrer Next.js-Site erfordert.
Der Ansatz ist sehr einfach, das Rendern des Inhaltsbereichs iteriert über jedes Element und verwendet eine Factory, um die zu rendernde Komponente zu bestimmen. Diese Fabrik erhält auch die Anzeigeoption, mit der die Blöcke in verschiedenen Größen gerendert werden können.
function ContentAreaRenderer(props :any) {
let items :any[] = props.items;
var factory = new componentFactory()
return(
{items?.map(i => {
const ContentAreaItem = factory.resolve(i);
const Component = ContentAreaItem.Component;
if (Component != null)
return (
)
else
return null
})}
)
}
Der ‘KomponentenFabrik‘ erhält die richtige Komponente zum Rendern und auch die richtige Anzeigeoption.
class ContentAreaItem {
ItemClasses: string;
Component: any;
constructor () {
this.ItemClasses = "fullwidth"
}
}
interface Dictionary {
[Key: string]: T;
}
class componentFactory {
components: Dictionary = {};
constructor(){
this.components["JumbotronBlock"] = JumbotronBlock;
this.components["TeaserBlock"] = TeaserBlock;
}
getType(item: any) : string {
var contentTypes = item.ContentLink.Expanded.ContentType;
return contentTypes[contentTypes.length - 1];
}
getDisplayOption(item: any) : string {
return item.DisplayOption === "" ? "fullwidth" : item.DisplayOption;
}
resolve(item: any): ContentAreaItem {
var contentType: string = this.getType(item);
var i = new ContentAreaItem();
i.Component = this.components[contentType];
i.ItemClasses = this.getDisplayOption(item);
return i;
}
}
Schritt 3 – Hosten der Website mit Vercel
Vercel ist eine Plattform für Websites, die mit Frontend-Frameworks erstellt wurden. Wenn Ihre Website mit Vercel gehostet wird, erhalten Sie aufgrund des Edge-Cachings automatisch Leistungsvorteile.
Einsatz
Die Bereitstellung Ihrer Website mit Vercel ist äußerst unkompliziert.
Erstellen Sie ein neues Projekt und verbinden Sie es mit dem GitHub-Repository und konfigurieren Sie den Quellspeicherort und die Build-Pipeline. Jedes Mal, wenn der Zweig aktualisiert wird, wird der Code erstellt und die Website automatisch bereitgestellt.
Dieser Ansatz hat einige echte Vorteile:
- Die Vorschau aller Änderungen ist einfach. Jeder Push an das Repository löst einen Build aus und generiert eine eindeutige URL, die geteilt werden kann.
- Es ist möglich, eine frühere Version auf „Produktion“ hochzustufen, d. h. einfach durch Klicken auf eine Schaltfläche zurückzusetzen.
Schritt 4 – Handhabung von Inhaltsänderungen
Statische Websites können eine atemberaubende Leistung erbringen, stellen jedoch Herausforderungen dar, wenn Inhalte geändert werden. Die Änderungen werden nicht wiedergegeben.
Es gibt mehrere Strategien, die Sie anwenden können, um dieses Problem zu lösen.
- Verlängerung auf Anfrage – Es ist möglich, eine Seite neu zu generieren, wenn eine Anfrage eingeht, aber so gedrosselt, dass eine X-Anzahl von Sekunden verstrichen sein muss, bevor die Seite neu generiert werden kann.
- On-Demand-Neuvalidierung – Sie können einen API-Endpunkt verfügbar machen, der beim Aufruf die spezifische Ressource neu generiert.
Das Problem mit Verlängerung auf Anfrage Wir bewegen uns von der statischen Generation zur dynamischen Generation.
import type { NextApiRequest, NextApiResponse } from 'next'
type ErrorData = {
message: string
}
type SuccessData = {
revalidated: boolean,
message: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.query.secret !== process.env.REVALIDATE_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
const { revalidatePath } = req.body;
try {
await res.revalidate(revalidatePath)
return res.json({ message: revalidatePath, revalidated: true })
} catch (err) {
return res.status(500).send({ message: 'Error revalidating :' + revalidatePath })
}
}
Im obigen Beispiel habe ich einen API-Endpunkt verfügbar gemacht. Der Anforderungstext enthält den Pfad der Ressource, die ungültig gemacht werden muss. Der ‘erneut validieren‘-Funktion löst dann die Neugenerierung der Seite aus.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IContentEvents contentEvents)
{
contentEvents.PublishedContent += ContentEvents_PublishedContent;
}
private void ContentEvents_PublishedContent(object sender, ContentEventArgs e)
{
if (e.Content is IRoutable routableContent)
{
var url = UrlResolver.Current.GetUrl(e.ContentLink);
Task.Run(() =>
{
var request = new RevalidateRequest { RevalidatePath = url };
Task.Delay(10000); // wait 10 seconds
var r = client.PostJsonAsync("/api/revalidate/?secret=...", request);
Task.WaitAll(new[] { r });
});
}
}
Der obige C#-Code zeigt, wie die Optimizely-Website die Revalidierung auf der statischen Website auslöst. Ich habe eine 10-Sekunden-Verzögerung eingebaut, da Sie zulassen müssen, dass der Inhalt mit dem Inhaltsdiagramm synchronisiert wird.
Abschließende Gedanken
Auch wenn Sie Ihre Inhalte wahrscheinlich nicht so reproduzieren werden, wie Sie es auf einer normalen Optimizley-Website tun würden, demonstriert dieser POC die Kernkonzepte bei der Verwendung von Optimizely als Headless-CMS.
Leistungsvorteile
Obwohl dies nicht der wissenschaftlichste aller Vergleiche ist, zeigen die beiden folgenden Leuchtturmberichte die Leistungsverbesserungen, die Sie erzielen können, wenn Sie zu einem statisch generierten Ansatz wechseln.
Nächster Artikel
Im nächsten Artikel werde ich mich mit der Verwendung von Optimizely in einem Headless-Modus befassen und auch andere Funktionen wie Suche, Inhaltsauflistung usw. demonstrieren.
Beispiele
Sie können auf den POC-Quellcode über mein GitHub-Konto und die statische Website unter https://graph-ql-three.vercel.app/ zugreifen.
So was:
Wie Wird geladen…