-
Notifications
You must be signed in to change notification settings - Fork 29k
Description
Goals
- Support global CSS effects (e.g. Bootstrap, Normalize.css, UX-provided, etc)
- Support stable component-level CSS
- Hot-reload style changes in development (no page refresh or state loss)
- Able to code-split for critical CSS extraction in production
- Leverage existing convention users are already familiar with (e.g. Create React App)
- Retain existing
styled-jsx
support, potentially more optimized
Background
Importing CSS into JavaScript applications is a practice popularized by modern day bundlers like Webpack.
However, it's tricky to get CSS imports right: unlike JavaScript, all CSS is globally scoped. This means it does not lend itself well to bundling. Especially bundling between multiple pages where styling is separated in a CSS file per page and load order matters.
By our measurements, more than 50% of Next.js users use Webpack to bundle .css
files, either through @zeit/next-css
, @zeit/next-sass
, @zeit/next-less
, or a custom setup. This is further verified through talking to companies. Some have all their styles through importing CSS, others use it for global styles and then use a CSS-in-JS solution to style components.
Currently we have these three plugins that allow you to import CSS, however, they have common issue in CSS ordering and certain loading issues. The reason for this is that @zeit/next-css
doesn't enforce a convention for global styles. Global CSS can be imported in every component / JavaScript file in the project.
To solve these common issues and make it easier for users to import CSS we're planning to introduce built-in support for CSS imports. This will be similar to styled-jsx where if you don't use the feature there will be no built-time or runtime overhead.
This effort also allows us to improve developer experience of the solution.
Proposal
Next.js should support modern CSS and down-level it on behalf of the user. This approach would be similar to how we support modern JavaScript and compile it down to ES5.
By default, we should:
- Use Autoprefixer to add vendor-specific prefixes automatically (without legacy flexbox support)
- Polyfill or compile Stage 3+ CSS features with their equivalents
- Automatically fix known "flexbugs" for the user
During development, CSS edits should be automatically applied, similar to JavaScript hot reloading.
In production, all CSS should be fully extracted from the JavaScript bundle(s) and emitted into .css
files.
Furthermore, this .css
should be code-split (when possible) so that only the critical-path CSS is downloaded on page load.
Global CSS
Next.js will only allow you to import Global CSS within a custom pages/_app.js
.
This is a very important distinction (and a design flaw in other frameworks), as allowing Global CSS to be imported anywhere in your application means nothing could be code-split due to the cascading nature of CSS.
This is an intentional constraint. Because when global CSS is imported in for example, a component, it will behave different when moving from one page to another.
Usage Example
/* styles.css */
.red-text {
color: red;
}
// pages/_app.js
import '../styles.css'
export default () => <p className="red-text">Hello, with red text!</p>
Component-level CSS
Next.js will allow pure CSS Modules to be used anywhere in your application.
Component-level CSS must follow the convention of naming the CSS file .module.css
to indicate its intention to be used with the CSS Modules specification.
The :global()
CSS Modules specifier is allowed when combined with a local class name, e.g. .foo :global(.bar) { ... }
.
Usage Example
/* components/button.module.css */
.btnLarge {
padding: 2rem 1rem;
font-size: 1.25rem;
}
.foo :global(.bar) {
/* this is allowed as an escape hatch */
/* (useful when controlling 3rd party libraries) */
}
:global(.evil) {
/* this is not allowed */
}
/* components/button.js */
import { btnLarge } from './button.module.css'
// import '../styles.css'; // <-- this would be an error
export function Button({ large = false, children }) {
return (
<button
className={
large
? // Imported from `button.module.css`: a unique class name (string).
btnLarge
: ''
}
>
{children}
</button>
)
}