Next.js CSS-Namespacing von Drittanbietern


In einem der Projekte, an denen wir unter centralsoft.io gearbeitet haben, mussten wir ein sehr interessantes Problem lösen. Wie können wir CSS von Drittanbietern in unsere Website einfügen und so enthalten, dass nur der beabsichtigte Inhalt davon betroffen ist?

Die Idee unseres Projekts ist es, HTML-Websites oder Snippets, die mit einem visuellen Webdesigner / CMS eines Drittanbieters (in diesem Fall Webflow) erstellt wurden, auf unserer in Next.js erstellten Hauptwebsite abzurufen und zu rendern.

Nachdem wir den Abruf- und Analyseprozess eingerichtet hatten, wurde im nächsten Schritt der extrahierte Inhalt auf unserer Website gerendert. Der Inhalt umfasst:

  • Metadaten
  • Stile (Inline und Links)
  • Skripte (Inline und Links)
  • HTML-Schnipsel

Das Laden von Metadaten unter dem Tag <head> ist mit der in der Komponente <Head> integrierten Next.js unkompliziert.
In ähnlicher Weise laden wir die Tags <script> und <style>.

<Head>
  ...
  {metadata && <Metadata metadata={metadata} />}
  {styles && <Styles links={styles.links} inline={styles.inline) />}
  {scripts && <Scripts scripts={scripts} />}
  ...
</Head>

Für den eigentlichen HTML-Inhalt verwenden wir etwa so "ReactousSetInnerHTML" von React:

<div
  className="webflow-wrapper"
  dangerouslySetInnerHTML={{ __html: content }}
/>

Dieser Ansatz funktioniert wie erwartet. Die Metadaten werden festgelegt, der Inhalt wird geladen, die Skripte werden ordnungsgemäss ausgeführt und der Stil wird angewendet.

Das Problem tritt auf, wenn das geladene CSS mit unserem eigenen benutzerdefinierten CSS auf der Seite kollidiert. Wenn wir uns die CSS-Dateien ansehen, die von "Webflow" generiert werden, können wir so etwas sehen:

...

.heading {
  ...
  display: -ms-flexbox;
  display: flex;
  font-size: 28px;
  ...
}

.main-content {
  ...
  padding-top: 100px;
  padding-bottom: 100px;
  ...
}

...

Dies ist sicherlich ein Problem. Wir müssen einen Weg finden, damit das auf unsere Website geladene CSS von Drittanbietern nicht mit unserem kollidiert.

Die Lösung besteht darin, den Inhalt jedes Style-Links herunterzuladen, ihn unter einen "scss-Namespace" zu packen, ihn in "css" zu kompilieren und zu laden.
Dieser Vorgang ist ziemlich zeitaufwändig, und dies bei jeder Anforderung einer Seite zu tun, ist definitiv keine gute Lösung.

Nest.js 9.3 https://nextjs.org/blog/next-9-3 brachte Kompatibilitäten für die statische Site-Generierung mit den Funktionen "getStaticProps" und "getStaticPaths".

Auf diese Weise können wir während der Erstellungszeit alle Stylesheets von Drittanbietern mit einem Namespace (Präfix) versehen. Die kompilierten Stile werden dann inline in die statische HTML-Datei geladen.

  • getStaticProps muss aus einer page -Komponente exportiert werden (im Verzeichnis / pages)
export async function getStaticProps (ctx) {
  // ...
  return {
	  props: {
      // ...
    },
  }
}
  • Herunterladen jedes Stylesheet-Links
async function downloadStylesheets(stylesheets: string[]): Promise<string[]> {
  const inline = []
  for (const sheet of stylesheets) {
    const res = await fetch(sheet)
    inline.push(await res.text())
  }
  return inline
}
  • Zusammenführen aller heruntergeladenen Inhalte unter einem Stylesheet
function merge(blocks: string[]): string {
  let merged = ""
  blocks.map((block) => {
    merged += block
    return block
  })
  return merged
}
  • Umschliessen des Stylesheet-Inhalts unter einen bestimmten Namespace
function wrap(namespace, content) {
  const wrapped = `
    .${namespace} {
        ${content}
    }
  `
  return wrapped
}
  • Kompilieren des scss zucss mitnode-sass. Der Grund, warum wir node-sass verwenden können, ist, dass wir Server Side während der Build-Zeit ausführen.
const compiler = require("node-sass")

function compile(scss: string): string {
  const css = compiler
    .renderSync({
      data: scss,
      outputStyle: "compressed",
    })
    .css.toString()
  return css
}
  • Endlich:
function DynamicPage({ content, style }) {
  return (
	  <>
      <div
        className="webflow-wrapper"
        dangerouslySetInnerHTML={{ __html: content }}
      />
	    {style && <style type="text/css">{style}</style>}
    </>
	)
}

function namespace(namespace: string, blocks: string[]): string {
  const merged = merge(blocks)
  const wrapped = wrap(namespace, merged)
  const compiled = compile(wrapped)
  return compiled
}

export async function getStaticProps (ctx) {
  // ...
  const content = "" // load content
  const extractedStylesheets = await extractStyleshets(content)

  const style = namespace("webflow-wrapper", extractedStylesheets)
  return {
	  props: {
      // ...
      style,
      content
    },
  }
}

export default DynamicPage

Namespacing (Präfix) CSS von Drittanbietern ist ein unkomplizierter Prozess. Die Verwendung von SCSS-Namespaces zum Umschliessen aller CSS von Drittanbietern und zum Kompilieren zu CSS ist zeitaufwändig und muss nach jeder Anforderung an unsere Website wiederholt werden.
Mit Next.js SSG (Static Site Generation) können wir diesen Prozess während der Erstellungszeit für alle unsere statischen Seiten ausführen.