To add Bootstrap to Next.js, run npm install bootstrap and import bootstrap/dist/css/bootstrap.min.css once. Put the import in app/layout.js for the App Router or in pages/_app.js for the Pages Router. That one line brings in the entire stylesheet, component styles included.
Bootstrap’s JavaScript is a separate step. The bundle reads window and document, so it has to load inside a client-side effect and not at import time.
The stylesheet is the bigger file. In the Bootstrap 5.3.8 package, the minified CSS comes to 232,111 bytes according to jsDelivr’s 2026 file listing, while the bundled script is 80,496 bytes.
How to Add Bootstrap to a Next.js Project
Run npm install bootstrap in a create-next-app project, import the minified stylesheet once in the root layout, and start putting Bootstrap classes in your JSX. Every CSS feature works after that, from the grid to the component styles.
The JavaScript is a separate job, and it is where most setups break.
If Bootstrap itself is new to you, this primer on Bootstrap covers what ships in the package. The install below doesn’t need any of that background.
Installing with npm

- Create the project with
npx create-next-app@latest my-appand pick the router you want when the prompt appears. - Install the package with
npm install bootstrap. - Import the minified stylesheet in
app/layout.js(App Router) orpages/_app.js(Pages Router). - Add classes such as
btn btn-primaryto a component, runnpm run dev, and confirm the button renders with Bootstrap styling.
Bootstrap 5 does not need jQuery, according to the Bootstrap 5.3 documentation.
On the script side, Bootstrap’s introduction page gives you the bundle, which has Popper built in, or bootstrap.min.js plus a separate Popper script. In Next.js the bundle is the simpler pick.
When a CDN link is enough
A CDN link suits a prototype and little else.
Bootstrap recommends jsDelivr and uses it in its own docs, with an integrity hash and a crossorigin attribute on every tag. In the App Router a plain stylesheet link works too, since the Next.js documentation notes that React 19 supports link elements for stylesheets.
Sass customization is off the table, because you load compiled CSS. Unless the URL pins a release such as 5.3.3, the version can drift. And a cold load makes one more third-party request, which is a small cost but a real one.
Anything that ships to real users belongs on npm, where the version lives in package.json and the build imports the stylesheet directly.
Where the Bootstrap CSS Import Goes in the App Router and the Pages Router
In the App Router, the import goes in app/layout.js. The Pages Router wants it in pages/_app.js.
The Next.js documentation (16.4) uses Bootstrap as its example for external stylesheets in both routers, which makes these the safest locations.
Order matters more than location. Next.js sorts CSS by import order and merges stylesheets in production builds, so import Bootstrap before your own files.
That order is also the first tool for overriding Bootstrap CSS before anyone reaches for !important.
The starter globals.css can stay, but it must load after Bootstrap or its rules can lose to Bootstrap’s base styles. CSS Modules keep component rules scoped, so they sit next to Bootstrap without class name collisions.
App Router
// app/layout.js
import "bootstrap/dist/css/bootstrap.min.css";
import "./globals.css";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Stylesheets from external packages can be imported anywhere inside the app directory, including colocated components. The root layout is still the right home for Bootstrap, because it is global.
Next.js builds on React’s built-in stylesheet support, so global styles are not removed when you move between routes, and that can cause conflicts. Import Bootstrap once, in the root layout, and leave it there.
Pages Router
The import is a single line in pages/_app.js, because the docs ask for global stylesheets to live in _app.js to avoid conflicts. Since Next.js 9.5.4, CSS from node_modules may be imported anywhere, but Bootstrap still belongs in _app.js.
A third-party component’s own stylesheet, like a dialog’s CSS, can live inside that component. Bootstrap cannot, because every page needs it.
Moving from the Pages Router to the App Router means moving this one line from _app.js to the root layout.
How Do You Load Bootstrap JavaScript Without Breaking Server Rendering?
Import bootstrap/dist/js/bootstrap.bundle.min.js inside a useEffect in a client component, so the script runs only in the browser. The bundle reads window and document, and neither exists while Next.js renders on the server.
LogRocket’s Bootstrap guide (updated September 2024) traces the typical Bootstrap errors in Next.js back to server-side rendering. The Next.js documentation (16.4, October 2026) lists effects and browser APIs such as window among the reasons to use a Client Component.
Pages that only use the grid and utility styling need none of this. The script matters for the collapsing Bootstrap navbar, dropdowns, tooltips and carousels.
Dropdowns, popovers and tooltips also depend on Popper, and the bundle file already contains it. Bootstrap’s docs say to include either bootstrap.js or its minified twin, never both.
Client component with useEffect
Create a loader that renders nothing:
"use client";
import { useEffect } from "react";
export default function BootstrapClient() {
useEffect(() => {
import("bootstrap/dist/js/bootstrap.bundle.min.js");
}, []);
return null;
}Render it once in app/layout.js, right after {children} inside the body tag. In the Pages Router, call the same dynamic import from a useEffect in pages/_app.js.
The dynamic import matters. Client Components are also prerendered into HTML on the server, per the Next.js documentation, so a static import at the top of the file still runs there and fails.
next/script and next/dynamic
The next/script component can load the jsDelivr bundle with strategy="afterInteractive", which is the default. Its strategy prop also takes beforeInteractive, lazyOnload or an experimental worker (Next.js script reference).
The onLoad, onReady and onError handlers work only in Client Components. A beforeInteractive script must sit in the root layout and cannot use onLoad. The worker strategy doesn’t work with the App Router at all.
For Pages Router projects, next/dynamic with server rendering switched off keeps a Bootstrap-dependent component out of the server render, a pattern LogRocket also documents.
With either approach, a trigger clicked before the script finishes loading does nothing. A Bootstrap modal button is where developers usually notice it first.
Do Server Components need “use client”?
Only the loader does.
Layouts and pages are Server Components by default, so Bootstrap class names in your markup render on the server like any other HTML. A “use client” file pulls its imports and the components it renders directly into the client bundle, which is why the loader returns null instead of wrapping the page.
The limit sits in Bootstrap itself. Its docs say the JavaScript is not fully compatible with frameworks like React, because both may change the same DOM element, which produces bugs such as dropdowns stuck open.
Bootstrap’s own docs name React-Bootstrap as the alternative for React.
React-Bootstrap, reactstrap or Plain Bootstrap Classes: Which Fits Next.js?
Use plain Bootstrap classes for mostly static pages, React-Bootstrap when the interface is interactive, and reactstrap only if your team already knows it.
All of them give you the same prebuilt Bootstrap components. What differs is who runs the JavaScript.
| Option | Install | Bootstrap JS | Best for |
|---|---|---|---|
| Plain Bootstrap classes | bootstrap | bootstrap.bundle.js, loaded by you | Static pages, marketing sites |
| React-Bootstrap | react-bootstrap bootstrap | Rewritten as React components | Dashboards, interactive forms |
| reactstrap | reactstrap react react-dom bootstrap | None, Popper through react-popper | Teams that prefer composition props |
Plain Bootstrap classes give you the smallest dependency list, and Server Components render the markup untouched. You also get full access to Bootstrap’s Sass. The price is that you own the JavaScript loader and the DOM conflicts that come with it.
React-Bootstrap replaces Bootstrap’s JavaScript with React components, so there is nothing to load by hand. It imports one component at a time (react-bootstrap/Button), which the docs say can cut the code sent to the client. It ships no CSS, though, so the stylesheet import covered earlier still applies. It is also not automatically compatible with themes that extend Bootstrap’s JavaScript behavior.
reactstrap offers stateless components for Bootstrap 5 with no jQuery and no Bootstrap JavaScript, and content passes through children, which keeps JSX readable. On the downside, it has no bundled CSS and the README setup targets create-react-app, so the Next.js import location is yours to work out. Popper arrives through react-popper, one more dependency for tooltips, popovers and dropdowns.
Where a component uses state or effects, the Next.js documentation says to wrap it in your own Client Component, unless the package already declares “use client” in its entry point. Check the package before assuming either way.
On versions, React-Bootstrap v2.10.10 and v3.0.0-beta.5 both target Bootstrap 5, while v1.6.1 covers Bootstrap 4 (React-Bootstrap docs). reactstrap keeps Bootstrap 4 support on v8, according to its README.
My pick: skip the manual loader once a project has more than a couple of interactive components. Plain classes plus the bundle script stay fine for a navbar toggle and little else.
Bootstrap’s JavaScript docs also link a working Next.js demo with React-Bootstrap in the twbs/examples repository.
Customizing Bootstrap in Next.js with Sass
Install sass as a dev dependency, write a custom.scss that sets your variable overrides before Bootstrap’s Sass loads, and import that file instead of bootstrap.min.css. Next.js compiles .scss and .sass files once the package is installed, according to its Sass guide (16.4, updated September 2026).
Import one or the other, never both. Loading the compiled stylesheet and your custom build ships Bootstrap twice.
In the App Router the line is import "./custom.scss"; in app/layout.js. In the Pages Router it becomes import "../styles/custom.scss"; in pages/_app.js.
Compiler settings live in sassOptions inside next.config.js. The documented keys include additionalData and implementation, which defaults to the sass package, with sass-embedded as the alternative.
additionalData prepends code to every Sass file. Putting Bootstrap’s import there would compile the framework into each one.
React-Bootstrap’s docs suggest installing vanilla Bootstrap next to it when you plan to customize Sass, so this workflow applies to that library too.
Overriding variables
Overrides go after Bootstrap’s functions and before its variables.
Every Bootstrap variable carries the !default flag, so a value you assign first is never replaced by Bootstrap’s own (Bootstrap Sass documentation).
// app/custom.scss
@import "../node_modules/bootstrap/scss/functions";
// Your overrides
$primary: #6f42c1;
$enable-shadows: true;
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/variables-dark";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/root";
// Optional parts
@import "../node_modules/bootstrap/scss/utilities";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/buttons";
@import "../node_modules/bootstrap/scss/helpers";
@import "../node_modules/bootstrap/scss/utilities/api";Theme colors are standalone variables such as $primary and $danger, so setting them updates the $theme-colors map. Deleting keys is riskier, because Bootstrap expects primary, success and danger to exist.
Breakpoints sit in a Sass map as well, and Bootstrap compiles them into the media queries used across the stylesheet. Change $grid-breakpoints first and recheck every layout.
Importing only the partials you need
Bootstrap’s docs favor picking parts over importing everything. They suggest starting from the full stack in bootstrap.scss and deleting what you skip.
Functions, variables, variables-dark, maps, mixins and root are required. Reboot, type, containers, grid, buttons, modal and the other component partials are optional. The usual failure is removing a partial that another one depends on.
That failure breaks the compile or the styles, and the docs warn that dependencies across partials make some omissions harder.
Compiling from source also adds build work on every edit to custom.scss, which the precompiled file avoids.
Fixing Bootstrap Errors and Sass Warnings in Next.js
Most problems fall into crashes that name window or document, components that do nothing, hydration warnings and Sass deprecation notices.
| Symptom | Origin | Cause | Fix |
|---|---|---|---|
| window or document is not defined | Bootstrap JS at import time | Browser globals touched during prerender | Dynamic import inside useEffect |
| Dropdown, modal or navbar does nothing | Bootstrap JS | Loader not running, or wrong data attributes | Check the loader, then data-bs names |
| Hydration mismatch | React | Server HTML differs from first browser render | Effect, next/dynamic, or suppressHydrationWarning |
| legacy-js-api warning | Next.js Sass tooling | Legacy Sass JS API still called | Newer Next.js, or silence it |
| import, global-builtin, color-functions | Bootstrap’s Sass source | Bootstrap 5 uses @import and global functions | Silence now, plan for Bootstrap 6 |
Window and document errors
Both errors come from Node running code that expects a browser, which usually happens during next build or the first server render of a route.
Next.js documents a guard. A module that touches window can import the client-only package, so a wrong import fails with a clear message. The package is optional, per the Next.js documentation (16.4, October 2026).
Do not patch the error with a typeof window check inside render logic. Next.js lists that pattern as a cause of hydration errors.
Components that do not respond
- If the loader seems silent, put a
console.loginside the effect to confirm it runs at all. - Bootstrap allows one set of data attributes per element, so a single button cannot open a modal and show a tooltip.
- Data attribute options use kebab-case, such as
data-bs-custom-class, never camelCase. - Programmatic methods are asynchronous, so a call on a component that is still transitioning is ignored.
Calling dispose straight after hide gives wrong results, per Bootstrap’s JavaScript docs. Wait for the hidden event, such as hidden.bs.modal.
For a Bootstrap dropdown that opens but never closes, look for a second handler or a React state toggle on the same element.
Hydration mismatches
A hydration warning means the server HTML and the first browser render disagree.
The Next.js docs name invalid nesting as a common cause, such as a div inside a p or a link inside a link, which is easy to introduce when pasting example markup. Reading browser-only APIs during render does it too. So can a CDN that rewrites the HTML, and the docs name Cloudflare Auto Minify as an example.
Start by rendering client-only content after an effect runs. If that doesn’t fit, load the component with next/dynamic and server rendering off. suppressHydrationWarning comes last, since it works one level deep and React does not patch mismatched text when it is set.
Sass deprecation warnings by origin
Where a Sass warning comes from decides whether you can do anything about it.
The legacy-js-api warning belongs to Next.js tooling. Dart Sass has warned about the legacy JS API since 1.79.0 and removes it in 2.0.0 (Sass documentation).
The import, global-builtin and color-functions warnings come from Bootstrap’s own Sass source. Sass deprecated @import in 1.80.0, and Bootstrap 5 is written with it.
Bootstrap’s documentation says these notices do not block compilation and can be ignored while a long-term fix is in progress. A Bootstrap maintainer wrote in the issue tracker that version 5 will not be changed, that version 6 moves to Sass modules on Dart Sass only, and that silencing is the best option for now.
Sources disagree on when Next.js dealt with its own warning. A Japanese developer write-up says 15.0.1, while a Next.js maintainer on issue 71638 said the fix should land in 15.1, with the silencing change first shipping in canary 15.0.4-canary.12.
The issue thread is the maintainers’ own record, so trust it over the write-up and confirm in your own build output.
Sitecore’s upgrade guide for Next.js 15.3.1 apps still adds both import and legacy-js-api to sassOptions as a temporary fix for Bootstrap’s Sass warnings.
// next.config.js
const nextConfig = {
sassOptions: {
silenceDeprecations: ["import", "global-builtin", "color-functions", "legacy-js-api"],
},
};
module.exports = nextConfig;Sass treats import and global-builtin as separate deprecations, so silencing one leaves the other printing.
quietDeps only covers stylesheets reached through a load path, and relative imports from your entry file count as your own code. It will not hide warnings from a custom.scss built with relative paths.
Silencing hides the warning without removing the cause, and the legacy API disappears entirely in Dart Sass 2.0.0. A commenter on the Next.js issue reports that warnings keep printing after a config change until the .next directory is deleted.
How Much Does Bootstrap Add to a Next.js Production Build?
The full minified stylesheet is 232,111 bytes and the bundled script is 80,496 bytes, going by jsDelivr’s file listing for the Bootstrap 5.3.8 package.
Those are file sizes before compression. Bootstrap’s optimize page says to serve minified, compressed files, and Next.js minifies and code-splits CSS only in production builds, so measure after next build.
bootstrap.min.cssat 232,111 bytesbootstrap.bundle.min.jsat 80,496 bytes, Popper includedbootstrap.min.jsat 60,539 bytes, Popper excludedbootstrap-grid.min.cssat 51,789 bytes, grid and flex utilities only
The gap between the two script files is Popper. Bootstrap’s docs also let you import single plugins from bootstrap/js/dist in your own bundler instead of loading everything.
useEffect(() => {
import("bootstrap/js/dist/collapse");
import("bootstrap/js/dist/dropdown");
}, []);Dropdowns, tooltips and popovers still need Popper listed in package.json when you go this route, per the same page.
Utilities are the biggest slice of the stylesheet, and bootstrap-utilities.min.css alone is 85,457 bytes. Bootstrap generates those classes from a Sass map through the utilities API import, so a smaller map yields a smaller file.
A layout that needs only a grid system can import bootstrap-grid.min.css and skip Sass entirely.
For a marketing site that uses a grid, buttons and a navbar, partial imports repay the build step. For an admin dashboard that touches most components, the saving shrinks and the full file is fine.
Trim CSS and JavaScript together. Bootstrap’s docs say to remove the related JavaScript when you drop a Sass partial, or the script keeps code for components that no longer have styles.
Bootstrap’s page has no prebuilt PurgeCSS example and points to community walkthroughs. Treat purging as a do-it-yourself job and compare the build output before and after.
Bootstrap in Next.js FAQ
Should you use Bootstrap or Tailwind CSS in a Next.js project?
Pick Bootstrap for prebuilt components and Sass theming, and Tailwind for utility classes in a custom design. The Next.js CSS documentation (16.4) lists Tailwind first and recommends it for most styling needs.
A closer look at Tailwind versus Bootstrap weighs the trade-offs in detail.
How do you add Bootstrap Icons to Next.js?
Install the separate bootstrap-icons package with npm i bootstrap-icons, then import bootstrap-icons/font/bootstrap-icons.css in the root layout and use classes such as bi bi-house.
The Bootstrap icon library holds over 2,078 icons and also ships as SVGs and an SVG sprite, so the font is optional.
How do you turn on Bootstrap dark mode in Next.js?
Add data-bs-theme=”dark” to the html element in the root layout. This needs Bootstrap 5.3.0 or later, and the same attribute on a single component scopes dark mode to that component.
Bootstrap ships no color mode picker, so a toggle needs your own client code that sets the attribute on the root element.
Why does TypeScript say it cannot find a declaration file for Bootstrap’s JavaScript?
TypeScript raises the error because the imported dist file has no type definitions of its own, so the module falls back to an implicit any. Installing the community @types/bootstrap package from DefinitelyTyped supplies them.
If you load the bundle only for its side effects, a one-line module declaration in a .d.ts file also silences the error.
Does Bootstrap’s Sass build under Turbopack?
Not in every reported setup. Next.js issue 86431, filed against version 16.0.3, describes Turbopack failing to resolve a Bootstrap Sass import, with a webpack build succeeding. It was first reported on Windows, and later commenters report the same failure on macOS and on versions 16.1.1 and 16.2.0.
Run next build with each bundler and check the issue’s status for your version before relying on one.
What to Recheck After a Next.js or Bootstrap Upgrade
After an upgrade, check Bootstrap in a fixed order, because the failures surface at different speeds. Sass warnings show up first, in the build output. A broken JavaScript loader stays silent, so click a dropdown or a navbar toggle on purpose. A broken stylesheet import is obvious the moment the page loads.
Silenced Sass warnings buy time, not a fix. The Sass documentation says it does not expect to remove @import before Dart Sass 3.0.0, which ships no sooner than two years after version 1.80.0.
Silencing also hides the removal notice, so rerun the build with silenceDeprecations off after each Dart Sass release. This position was verified on 11 October 2026, with Dart Sass at 1.105.1, and a Bootstrap release built on Sass modules would end the need to silence anything.
- What is Backend in Web Development? - September 13, 2026
- What is Frontend Development? - September 11, 2026
- How to Make a Button in Figma: Design Best Practices - September 10, 2026

