Load external script using JavaScript in Next.js

Viewed 1821

A while ago I made a relatively massive app for a single developer for my own personal use - a "social media" of sorts. I wrote it in React.js

I have recently decided to revisit the app and revamp it - primarily make it server-side rendered using Next.js.

The key problem is loading the themes. I had a feature where I would programmatically add an external script tag to the end of the body which would manipulate the canvas and show some animations.

I had no issues when I worked with React, as everything got loaded the way I expected it to, but when it comes to Next, that simply isn't the case. The script tag loads, but the code doesn't get executed.

The most important files I have issues with are:

  • My _document.jsx file:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { BASE_URL as base } from '../config'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html lang={"en"}>
        <Head>
            <link rel={"icon"} href={`${base}/short.png`} />
            <link
                rel={"apple-touch-icon"}
                href={`${base}/apple-icon-180x180-dunplab-manifest-34821.png`}
            />
            <link
                href="https://fonts.googleapis.com/css?family=Roboto&display=swap"
                rel={"stylesheet"}
            />
            <link
                rel={"stylesheet"}
                href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css"
                integrity={"sha256-zmfNZmXoNWBMemUOo1XUGFfc0ihGGLYdgtJS3KCr/l0="}
                crossOrigin={"anonymous"}
            />
            <link
                rel={"stylesheet"}
              href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
                integrity={"sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"}
                crossOrigin={"anonymous"}
            />
            <script
                defer
                src={`${base}/Vector2.js`}
                charSet={"utf-8"}
            ></script>
            <link rel={"manifest"} href={`${base}/manifest.json`} />
        </Head>
        <body>
            <div className="canvas-wrapper">
            <canvas id="canvas" className="canvas-bg"></canvas>
            <NextScript />
        </div>
        <Main>
            <script
                defer
                src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
                integrity={"sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"}
                crossOrigin={"anonymous"}
            ></script>
            <script
                defer
                src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
                integrity={"sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"}
                crossOrigin={"anonymous"}
            ></script>
            <script
                defer
                src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
                integrity={"sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"}
              crossOrigin={"anonymous"}
            ></script>
          </Main>
        </body>
      </Html>
    )
  }
}
  
export default MyDocument
  • My _app.jsx file:
import '../styles/globals.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import { ThemeProvider } from "../contexts/ThemeContext";
import { FontProvider } from "../contexts/FontContext";
import { DevProvider } from "../contexts/DevContext";
import { LanguageProvider } from "../contexts/LanguageContext";
// import { SocketProvider } from "../contexts/SocketContext";
import { ColourProvider } from "../contexts/ColourContext";
import PageContent from '../components/layout/PageContent/PageContent';
import { useStore } from '../store';
import { useEffect } from "react"
import { Provider } from 'react-redux';
import Head from 'next/head';
import { BASE_URL as base } from '../config';
import Nav from "../components/layout/Nav/Nav"
import setAuthToken from '../utils/setAuthToken';
import { getUser } from '../actions/auth';

function MyApp({ Component, pageProps }) {
  const store = useStore(pageProps.initialReduxState);
  useEffect(() => {
    if (localStorage.getItem('token')) {
      setAuthToken(localStorage.getItem('token'))
    }
    store.dispatch(getUser())
  })
  return (
    <>
      <Head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta property="og:title" content="Impulse" />
        <meta property="og:description" content="Make an impact. Change minds." />
        <meta
          property="og:image"
          content={`${base}/favicon-96x96-dunplab-manifest-34821.png`}
        />
        <meta
          name="description"
          content="Welcome to Impulse - make an impact, change minds. Impulse is dedicated to your enjoyment and pleasure!"
        />
                <script defer src={`${base}/scripts/initlog.js`}></script>
        <script defer src={`${base}/scripts/blurfocus.js`}></script>
        <title>Impulse</title>
      </Head>
      
      <ColourProvider>
        <DevProvider>
          <FontProvider>
            {/* <SocketProvider> */}
              <ThemeProvider>
                <LanguageProvider>
                  <Provider store={store}>
                    <PageContent>
                      <Nav />
                      <Component {...pageProps} />
                    </PageContent>
                  </Provider>
                </LanguageProvider>
              </ThemeProvider>
            {/* </SocketProvider> */}
          </FontProvider>
        </DevProvider>
      </ColourProvider>
    </>
  )
}

export default MyApp
  • My useScript.js hook
import { useRef } from "react"
import { BASE_URL as base } from "../config"
// actually, it's supposed to load a single script, not more of them
const useScripts = (script) => {
  const scriptLoaded = useRef(false);
  if (typeof window !== "undefined" && !scriptLoaded.current && script) {
    const element = document.createElement("script");
    element.src = `${base}/static/canvasThemes/${script}.js`;
    element.type = "text/javascript";
    const position = document.querySelector("head");
    position.appendChild(element);
    scriptLoaded.current = true;
  }
};

export default useScripts
  • My PageContent.jsx file, which was previously referenced in the _app.jsx file:
import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "../../../contexts/ThemeContext";
import { FontContext } from "../../../contexts/FontContext";
import { ColourContext } from "../../../contexts/ColourContext";
import StyledPageContent from '../../../styled/StyledPageContent';
import useScripts from "../../../hooks/useScript";
import cookie from 'cookie-cutter'

function PageContent(props) {
  const { isDarkTheme, toggleTheme } = useContext(ThemeContext);
  const { isLegacyFont } = useContext(FontContext);
  const { colour } = useContext(ColourContext)
  const [theme, setTheme] = useState("")
  useEffect(() => {
    const newOne = cookie.get("isDarkTheme")
    console.log("NJUUAN _APP", newOne);
    setTheme(() => newOne)
  }, [])
  useScripts(theme)

  return (
    <StyledPageContent
      isDarkTheme={isDarkTheme}
      isLegacyFont={isLegacyFont}
      colour={colour}
    >
      {props.children}
    </StyledPageContent>
  );
}

export default PageContent;

I'm looking for a way to add a script programmatically and immediately execute it.

Important: ALL script tags I want to add are considered "themes", which would grab the canvas from the _document and manipulate its background colour/fill/add text etc.

I have been trying a million different things for a month now and am starting to lose hope.

Not sure if I'll get any help, but thanks in advance all the same!

0 Answers
Related