Monday, February 5th 2018
Next.js 5: Universal Webpack, CSS Imports, Plugins and Zones
Posted byWe are very happy to introduce Next.js 5.0 to the world. It’s available on npm effective immediately. To upgrade, run:
npm i next@latest react@latest react-dom@latest
In addition to bumping Next.js, we upgrade the peer dependencies
react
andreact-dom
Next.js is a toolkit for universal, server-rendered (or statically pre-rendered) React.js applications. Getting started developing an application of any size is as easy as executing next
. (Read more.)
With every new release we are committed to retaining backwards compatibility, offering simple upgrade paths and only making API changes when absolutely necessary. Next.js 5.0 is no exception.
Under the hood, however, Next.js has undergone a radical transformation to enable powerful new use cases and extensibility. We started by making Next.js share a universal Webpack pipeline for both server and client code.
Universal Webpack and Next Plugins
Next.js leverages existing powerful tools like Webpack, Babel and Uglify, presented to the end-user as a remarkable simple interface: next
(to develop), next build
(to prepare for production) and next start
(to serve) or next export
to pre-render to static files.
One of the early decisions we made was to provide very powerful extensibility points for how these tools are configured. We didn't just want ease of use, we wanted to enable flexibility to extend the toolkit however you wanted.
For example, you can extend the Next.js webpack configuration by setting up a webpack
property in your next.config.js
.
Because webpack executes differently for production and development, we decided at the time to make it a function that decorates our default webpack config:
module.exports = {
webpack(config, { dev }) {
// modify it!
return config;
},
};
An example of the optional
next.config.js
file
However, Webpack would only execute on the client (browser) bundles, and you would miss out on the possibility of using this great toolchain for server-rendering.
We are happy to announce that we have extensively refactored our codebase to make Webpack work universally.
From your perspective, all that changes is that an additional isServer
property is passed to the decorator function above. However, the new semantics mean that the broad ecosystem of Webpack loaders are now available for you to use.
CSS, LESS, SASS, SCSS and CSS Modules
One of our most highly requested features is the ability to import CSS files and take advantage of Webpack loaders:
import './index.css';
export default function Index() {
return (
<div>
<p>I love CSS!</p>
</div>
);
}
An example page (
pages/index.js
) using CSS imports thanks to Universal Webpack
To make this work, you can bring the loaders you need as peer dependencies:
npm i --save css-loader style-loader postcss-loader
Next.js which gives you the freedom to pick and choose which loaders you need and to upgrade them to different versions at will.
And then extend the config to configure your loaders. In next.config.js
:
module.exports = {
webpack(config, options) {
const { dev, isServer } = options;
const extractCSSPlugin = new ExtractTextPlugin({
filename: 'static/style.css',
disable: dev,
});
config.module.rules.push({
test: /\\.css$/,
use: cssLoaderConfig(extractCSSPlugin, {
cssModules,
dev,
isServer,
}),
});
return config;
},
};
Extending the raw webpack config gives you great flexibility and control
While our general recommendation is to use component-local styling solutions, like the included styled-jsx
babel plugin, we believe CSS loaders have many important strengths, like making it easy to re-use existing CSS codebases and vastly simplifying migrating old codebases to Next.js.
Instead of enabling every conceivable feature and loader by default, we are introducing Next.js plugins, which are simple functions that decorate your configuration. Instead of manually extending the configuration to set up the loaders like we did above, you can just do:
const withCss = require('next-css');
module.exports = withCss({
/* extra optional config */
});
All it takes to enable importing
.css
files is to bring innext-css
Read more on CSS Loaders usage with Next.JS, or refer to some of the packages we have already created for you:
Our goal is to empower the community to develop and grow an ecosystem of practical simple extensions. To that end, we are opening the next-plugins monorepo for the Next.js community to maintain. All PRs are welcome!
TypeScript Support
One of the fastest growing technologies in the JavaScript ecosystem is TypeScript. So much so, that it is becoming officially supported by Babel 7, which means it will naturally be supported by Next.js by just customizing your .babelrc.
In the mean time, thanks to our new Universal Webpack support, you can already get full TypeScript support today!
You can extend your webpack config like this:
module.exports = {
webpack(config, options) {
const { dir, defaultLoaders } = options;
config.resolve.extensions.push('.ts', '.tsx');
config.module.rules.push({
test: /\\.+(ts|tsx)$/,
include: [dir],
exclude: /node_modules/,
use: [
defaultLoaders.babel,
{ loader: 'ts-loader', options: { transpileOnly: true } },
],
});
return config;
},
};
All we have to do is enable the
ts-loader
Like CSS loaders and pre-processors, TypeScript has been one of the most requested features. To make incorporating it into projects just as easily as any other loader, we now have a next-typescript
plugin you can include in your next.config.js
file:
const withTs = require('next-typescript');
module.exports = withTs({
/* additional config*/
});
Plugins can be trivially composed: they are just functions
Better Support for React Altlibs & Module Overloading
Many drop-in replacement implementations of React have emerged over time. Among them, some notable ones are [preact
](https://preactjs.com/), nervjs
and inferno
.
Other libraries focus on replacing the DOM renderer, like react-dom-lite
, which is aiming to make a smaller React build by introducing some minor tradeoffs in browser compatibility.
Universal Webpack support makes the process of incorporating these libraries as drop-in replacements even easier. In the same vein as the other plugins, this is all you have to do to use Next.js with preact:
npm i @zeit/next-preact preact preact-compat
We install the preact plugin and necessary peer dependencies
const withPreact = require('@zeit/next-preact');
module.exports = withPreact();
Our new
next.config.js
ready for preact
Check out the very simple @zeit/next-preact module or create your own!
Optional External Sourcemaps in Production
Now that webpack is used by Next.js for both client and server code, enabling source-maps in production builds is just one small adjustment to its configuration.
In development source maps are automatically enabled, so we configure it differently for production:
module.exports = {
webpack(config, { dev }) {
if (!dev) {
config.devtool = 'source-map';
}
return config;
},
};
We simply configure the
devtool
option differently when not in development
Zones
One of the stated goals of Next.js from the very beginning was to bring back and preserve the simplicity of the Web.
Server-rendering, a simple and agnostica approach to data fetching and declarative pages based on the filesystem structure are some of the features we introduced in line with this thinking.
A frequently overlooked aspect of web services and web sites is how naturally composable and scalable they are.
For example, mydomain.com/settings
and mydomain.com/
could be two entirely different apps, deployed independently, scaled independently, even running different versions of the same software.
All it takes to "glue them together" into a uniform experience for the end-user is some simple configuration of the backend routing layer or load balancers that expose them to the world. We are very happy to now bring the ability to compose multiple applications built with Next.js, connected together using normal <Link>
components. We call this feature Zones.
As an example, consider these two independent Next.js applications deployed to Vercel:
- https://front.vercel.app
- https://zeit-docs-zbqbsrucga.vercel.app/docs https://zeit-docs-zbqbsrucga.vercel.app/api
When we revamped our docs, we wanted to make accepting a community contribution as easy as possible.
We decided to split out the documentation "mini-website" into its own repository. Additionally, whenever a pull-request is submitted and a change is proposed, we deploy it automatically, in isolation:
What we ended up is two zones, that are brought together into the parent domain https://vercel.com
using our path alias feature. It looks something like this:
{
"rules": [
{ "pathname": "/docs", "dest": "our-docs.vercel.app" },
{ "pathname": "/api", "dest": "our-docs.vercel.app" },
{ "dest": "my-main-website.vercel.app" }
]
}
These simple rules allow you to compose microservices and zones together
All that's left is to invoke a now alias
command:
now alias -r rules.json my-domain.com
Our mission is to make deployment as universal and open as possible. To aid with local development, we recently open sourced micro-proxy
, a tool that works with the same configuration format seen above.
You are similarly able to join zones together with other solutions like Nginx, HAProxy or API Gateway.
Faster Production Build Times
We think developer experience and user experience go hand in hand. The more efficiently changes can be written, tested and deployed, the faster new features are added, bugs are squashed and overall user experience improves.
Consequently, we remain focused on continuously iterating on the performance profile of the most basic building blocks of the system.
With Next.js 5.0 we had an opportunity to take a look at next build
again, the command you run prior to deploying to production or exporting your Next.js application as a static site.
We are happy to report that for vercel.com, a React app made up of thousands of components, we have seen pretty dramatic improvements with Next.js 5.0, to the tune of 23.6% faster production build times:
Improved Caching for Dynamic Imports
Whenever you use dynamic import()
, this signals to WebPack that a new code-splitting entry-point exists.
At build time, this means generating a specific bundle for the corresponding subtree of modules.
Prior to Next.js 5.0, the dynamic bundles would receive a URL that resembles the following:
/_next/1517592683901/webpack/chunks/components_hello1_1345d10fc951cd6717c5676c467579a6.js
Now, we have turned dynamic imports into content-addressable hashes of the contents of the subtree:
/_next/webpack/chunks/components_hello1_1345d10fc951cd6717c5676c467579a6-b7874680a9e21fb6eb89.js
This means that across deployments, your users and customers won’t have to needlessly re-download the code they’ve already used.
Fragments
Next.js build a top-level <Document>
component that gets server-rendered with each page. Overloading this component puts you in complete control over your markup, enabling many advanced use cases.
Part of that initial markup is the list of scripts that Next.js needs to evaluate on the client side. A custom _document
looks like this:
import Document, { Head, Main, NextScript } from 'next/document';
export default class extends Document {
render() {
return (
<html>
<Head />
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
Document
allows you to customize the entire server-rendered output of a page
Until recently, we were forced to wrap our scripts inside a <div>
.
With Next.js 5.0, we now take advantage of the new Fragment
support, which translates into more lightweight pages and complete control over the styling of the page, with no extraneous markup.
More Accurate Errors
Node.js doesn’t support source maps, errors that happen on the server side are accompanied with a stack trace pointing to the compiled code.
With Next 5 we’ve improved source map support on the server side. Errors that happen when server rendering now point to the correct function and line number.
Conclusion
Universal Webpack solidifies the foundations of Next.js and makes it even more future proof. By and large, there is no longer an artificial separation of what plugins or loaders are applicable to Next.js and which ones aren’t.
In the spirit of zero configuration, we are excited to introduce Next Plugins, the community repository for recipes that automatically expand the functionality of Next.js, without having to tweak specific knobs.
With this, we now support the entire spectrum of CSS solutions, compile-to-js languages like TypeScript and React alternatives like Nerve, by just bringing an additional module and being explicit about its inclusion in next.config.js
. Simplicity without obscurity.
Zones allow interconnecting Next.js apps that are not rooted in the same repository or even server(s). We consider this a very important milestone in the "team scalability" category of improvements.
Next.js thus becomes a great candidate for large applications maintained by multiple teams. They can now deploy improvements concurrently, reducing error surfaces, increasing iteration speed and even trying out different technologies in addition to our core, like the many different approaches to state management or data fetching.
We want to take this opportunity to thank Deep Varma and the Trulia engineering team for contributing key insights, code and testing that led to the design of this feature.
As always, this release wouldn't have been possible without the many open-source contributors and our wonderful community.