Where to import bootstrap js file in next js 13 using new "app directory"
Asked Answered
L

6

7

I am using new app directory in next js 13 and when using bootstrap dropdown only the UI is working, js features of bootstrap are not working in it. I tried importing js file in layout.tsx but it throws this error =>

document not defined

enter image description here

Layout.tsx

import 'bootstrap/dist/js/bootstrap.min.js';
import './globals.css';
import './styles/index.scss';

export const metadata = {
    title: 'Create Next App',
    description: 'Generated by create next app',
};

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html lang='en'>
            <body>{children}</body>
        </html>
    );
}
Louls answered 24/4, 2023 at 7:6 Comment(0)
H
5

I defined null component with useEffect importing bootstrap js in seperate file with "use client" directive, and included that component in RootLayout:

importBsJS.tsx

'use client'
import { useEffect } from "react";

export default function ImportBsJS() {
  useEffect(() => {
    require("bootstrap/dist/js/bootstrap.bundle.min.js");
  }, []);
  return null;
}

layout.tsx

import "bootstrap/dist/css/bootstrap.min.css";
import ImportBsJS from "@/components/importBsJS";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ImportBsJS />
        <div className="container-lg">{children}</div>
      </body>
    </html>
  );
}

Heterogamy answered 16/7, 2023 at 16:37 Comment(0)
A
3

I found that using the "dynamic" function works to import the Bootstrap CSS always and the Bootstrap JS only on the client side in Next.js 13 in the layout.tsx file:

import './globals.css'
import { Inter } from 'next/font/google'
import 'bootstrap/dist/css/bootstrap.css';

// import '../styles/globals.css';
import '@fortawesome/fontawesome-free/css/all.min.css';

import dynamic from 'next/dynamic';

const DynamicBootstrap = dynamic(
  () => require('bootstrap/dist/js/bootstrap.min.js'),
  { ssr: false }
);

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {

  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
Atrophy answered 12/5, 2023 at 16:15 Comment(2)
Wouldn't this cause a "content flash" on page load where things are unformatted for a second?Alexisaley
This worked for me. @sprite7, it doesn't cause any FOUC because the CSS is imported directly into the markup and only the JS is dynamically loaded after rendering. So, this is the common pattern we are using on the client side anyway. Of course it requires to adhere some basic rules of progressive enhancement (e.g. no element visibility changes via JS onload)Charybdis
S
2

When this fails server-side:

import * as Bootstrap from "bootstrap"

you can replace it with the following:

app/providers/bootstrap.js

"use client"

import React, { createContext, useContext, useEffect, useState } from "react"

const Context = createContext(undefined)

function Providers({ children } {
  const [Bootstrap, setBootstrap] = useState(undefined)

  useEffect(() => {
    if (!Bootstrap) {
      const BS = require("bootstrap/dist/js/bootstrap.bundle.min.js")
      setBootstrap(BS)
    }
  }, [])

  return (
    <Context.Provider value={{ Bootstrap }}>
      {children}
    </Context.Provider>
  )
}

export const useBootstrap = () => {
  const context = useContext(Context)

  if (context === undefined) {
    throw new Error("useBootstrap must be used inside BootstrapProvider")
  }

  return context
}


export default Providers

app/layout.tsx

import "bootstrap/dist/css/bootstrap.min.css"
import "../styles/_global.scss"

import { Metadata } from "next"

import BootstrapProvider from "@/providers/bootstrap"

export const metadata: Metadata = {}

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <BootstrapProvider>
          {children}
        </BootstrapProvider>
      </body>
    </html>
  )
}

and use it like this in the client-side component:

app/components/CollapsibleExample.js

"use client"

import { useBootstrap } from "@/providers/bootstrap"

export default () => {
  const { Bootstrap } = useBootstrap()

  const toggle = (event) => {
    event.preventDefault()
    new Bootstrap.Collapse(`#collapse-id`).toggle()
  }

  return (
    <a href="#" onClick={toggle}>Toggle</a>
  )
}
Staves answered 21/8, 2023 at 11:11 Comment(0)
C
1

Another option is to manually integrate the bootstrap JS (either via CDN, or better as self-hosted scripts) directly in your RootLayout, which is pretty solid and does work with server-rendered components as well.

This might look a little old-fashioned, but I preferred this way over the useEffect/dynamic approach since it is much more explicit and easier to control what is happening behind the scenes. It allows you to painlessly use bootstrap everywhere without worrying about SSR or CSR.

import Script from 'next/script';
import "bootstrap/dist/css/bootstrap.css";

export default function RootLayout ({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></Script>
        <Script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></Script>
      </body>
    </html>
  );
};

Note: I used the locally installed CSS from node_modules here for convenience. In a common scenario one would put the CSS and JS into the "/public" folder and just include everything directly from there (also to ensure that JS and CSS versions stay in sync).

Charybdis answered 15/7, 2023 at 13:16 Comment(2)
So does this mean that it would first render the root element, then add the js files? or will the js files be part of the project and get rendered before serving the page?Entreat
Sorry, I totally missed your question. The Script component renders a common HTML script tag with the defer attribute set afaik. So it should simply fit into the browser's natural page loading flow and does NOT "inline" the external scripts. If you want to "inline" the Javascript, there should be other options. However - I finally ditched Bootstrap entirely and am very happy using Tailwind UI together with some custom components.Charybdis
G
0

in your children you used document object and this error happend because the page was rendred before you be in the browser so the document is undefind for fix this use typeof document !== "undefind" && "do something"

Godfearing answered 24/4, 2023 at 8:13 Comment(1)
I tried this method, unfortunately it did not work for me.Louls
H
0

This can be done in 3 steps.

Step 1:

npm install bootstrap

Step 2: In layout.ts add the following line:

import 'bootstrap/dist/css/bootstrap.css';

Step 3: Create a new file in src/components/BootstrapJS.js and add the following:

    "use client";
    
    import { useEffect } from 'react';
    
    function BootstrapJS() {
      useEffect(() => {
        require('bootstrap/dist/js/bootstrap.bundle.min.js');
      }, []);
    
      return null;
    }

export default BootstrapJS;

Now in layout.js import the new component:

import 'bootstrap/dist/css/bootstrap.css';
import './globals.css';
import { Inter } from 'next/font/google';
import BootstrapJS from '@/components/BootstrapJS.js';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {children}
        <BootstrapJS />
      </body>
    </html>
  );
}
Histoplasmosis answered 31/5 at 2:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.