Skip to content

[RFC] CSS Support #8626

@Timer

Description

@Timer

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:

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>
  )
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions