6 | Component other: {t('other:GOOD_MORNING')}
7 | {(t('other:GOOD_MORNING') === 'GOOD_MORNING') ? (
8 | it's not translated because "Index" page was opened directly and it does not have "other" namespace explicitly requested
9 | ) : (
10 | it is translated because "Other" page has requested "other" namespace and now it is cached (even on Index page)
11 | )}
12 |
3 | );
--------------------------------------------------------------------------------
/Chapter07/7-4-now/pages/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import renderer from 'react-test-renderer';
3 | import Index from './index.js'
4 |
5 | describe('Snapshot Testing', () => {
6 |
7 | it('Renders "Hello, World!"', () => {
8 | expect(renderer.create().toJSON()).toMatchSnapshot();
9 | });
10 |
11 | });
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Packt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Next.js Quick Start Guide
5 |
6 |
7 |
8 | This is the code repository for [Next.js Quick Start Guide](https://www.packtpub.com/web-development/nextjs-quick-start-guide?utm_source=github&utm_medium=repository&utm_campaign=9781788993661), published by Packt.
9 |
10 | **Server-side rendering done right**
11 |
12 | ## What is this book about?
13 | Next.js is a powerful addition to the ever-growing and dynamic JavaScript world. Built on top of React, Webpack, and Babel, it is a minimalistic framework for server-rendered universal JavaScript applications. This book will show you the best practices for building sites using Next. js, enabling you to build SEO-friendly and superfast websites.
14 |
15 | This book covers the following exciting features:
16 | * Explore the benefts of server-side rendering with Next.js
17 | * Create and link JavaScript modules together by understanding code splitting and bundling
18 | * Create website pages and wire them together through website navigation
19 | * Extend your application with additional Webpack loaders and features, as well as custom Babel plugins and presets
20 | * Use GraphQL and Apollo frameworks with Next.js to fetch data and receive push notifcations
21 |
22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1788993667) today!
23 |
24 |
26 |
27 |
28 | ## Instructions and Navigations
29 | All of the code is organized into folders. For example, Chapter02.
30 |
31 | The code will look like the following:
32 | ```
33 | import 'isomorphic-fetch';
34 |
35 | (async () => {
36 | const res = await fetch(...); // already polyfilled
37 | })();
38 | ```
39 |
40 | **Following is what you need for this book:**
41 | This book is for JavaScript developers who want to learn how to generate server-rendered applications.
42 |
43 | With the following software and hardware list you can run all code files present in the book (Chapter 1-7).
44 |
45 | ### Software and Hardware List
46 |
47 | | Chapter | Software required | OS required |
48 | | -------- | ------------------------------------| -----------------------------------|
49 | | 1 | Node JS | Windows, Mac OS X, and Linux (Any) |
50 | | 2 | Node JS | Windows, Mac OS X, and Linux (Any) |
51 | | 4 | Node JS | Windows, Mac OS X, and Linux (Any) |
52 | | 5 | Node JS | Windows, Mac OS X, and Linux (Any) |
53 | | 6 | Node JS | Windows, Mac OS X, and Linux (Any) |
54 | | 7 | Node JS | Windows, Mac OS X, and Linux (Any) |
55 |
56 |
57 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](http://www.packtpub.com/sites/default/files/downloads/NextDotjsQuickStartGuide_ColorImages.pdf).
58 |
59 |
60 | ### Related products
61 | * Full-Stack React Projects [[Packt]](https://www.packtpub.com/web-development/full-stack-react-projects?utm_source=github&utm_medium=repository&utm_campaign=9781788835534) [[Amazon]](https://www.amazon.com/dp/1788835530)
62 |
63 | * React: Cross-Platform Application Development with React Native [[Packt]](https://www.packtpub.com/web-development/react-cross-platform-application-development-react-native?utm_source=github&utm_medium=repository&utm_campaign=9781789136081) [[Amazon]](https://www.amazon.com/dp/1789136083)
64 |
65 | ## Get to Know the Author
66 | **Kirill Konshi**
67 | Kirill Konshin is the principal software developer at RingCentral, the world's leading Cloud communications provider. He is a highly experienced professional in full-stack web engineering with more than 10 years of experience, proficient in all the most recent web technologies. He is also an active open source contributor to React-related projects. You can follow him on Medium.
68 |
69 |
70 | ### Suggestions and Feedback
71 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions.
72 | ### Download a free PDF
73 |
74 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost. Simply click on the link to claim your free PDF.
75 |
--------------------------------------------------------------------------------
/book/1-introduction.md:
--------------------------------------------------------------------------------
1 | For quite some time the client-server architecture was one of the most wide spread patterns in large scale software development. Even systems that run purely on one computer often are designed this way. This allows to clearly separate concerns: server takes care of heavy business logic, persistent storage, accessing data from third party services and so on and client is responsible solely for presentation to end users.
2 |
3 | This architecture also allows to have multiple clients connected to one backend: mobile apps, IoT devices, third party REST API consumers (e.g. external developers) and web, for example.
4 |
5 | In early days of web development it was not that way though. Server was responsible for everything. Usually it was a combination of DB, app itself, template engine, bunch of static assets (images, CSS and so on) all baked together into a monolithic app. Later on it became obvious that this kind of architecture does not scale well.
6 |
7 | Nowadays modern web is moving back to client-server architecture with clean separation of concerns and concrete responsibilities of each component. Server side apps deal with data and client side apps deal with presentation of that data.
8 |
9 | We will cover following topics in this chapter:
10 |
11 | What is a single page app
12 | Introduction to React
13 | Single page apps performance issues
14 | Server side rendering with React
15 | What is a single page app
16 | Single page app implements such architecture for the web clients: the JavaScript app launches from a web page and then runs entirely on a client, all visual changes on website happen as a reaction on user actions and the data, that is fetched from the remote API server.
17 |
18 | It is called single page because server does not render pages for client, it always delivers same minimalistic markup required to bootstrap the JS app. All page rendering and navigation is happening purely on client using JavaScript which utilizes History APIs to dynamically swap page contents and URL in location bar.
19 |
20 | The advantages that this approach gives are: client can run something in background between page transitions, client does not have to re-download and re-draw the entire page in order to swap only the main content. Unfortunately, it also brings drawbacks, because now client is responsible for all state changes, synchronization of such changes across the entire interface, it must know when to load the data and what particular data. In other words, server generated app conceptually is a way simpler thing thanks to REST service + JS client.
21 |
22 | Creating JS Modules, code sharing, code splitting, bundling
23 | Separation of concerns is one of the key principles in software design, and since each entity in the code has to be isolated from others it makes sense to put them into separate files to simplify the navigation and ensure isolation.
24 | Modern JS applications consist of modules which can have exports and imports. JS modules export some entities and may consume exported entities from other modules.
25 |
26 | In this book we will use latest JS syntax with classes, arrow functions, spread operators and so on. If you are not familiar with this syntax you can always refer to it here: http://exploringjs.com.
27 |
28 | The simplest JS module looks like this:
29 |
30 | // A.js:
31 | export const noop = () => {};
32 | This file now has a named export noop which is a an arrow function that does nothing.
33 |
34 | Now in B.js we can import a function from file A.js:
35 |
36 | //B.js:
37 | import {noop} from "./A.js";
38 | noop();
39 | In real world, dependencies are much more complex and modules can export dozens of entities and import dozens of other modules, including those from NPM. Module system in JS allows to statically trace all dependencies and figure out ways to optimize them.
40 |
41 | If the client will download all JS in a straightforward way (download initial one JS file, parse it’s dependencies, and recursively download them and their deps) then load time will be dramatic. First of all because network interaction takes time, second, because parsing also takes time. Simultaneous connections are often limited by browser and HTTP 2.0
42 | (which allows to transfer many files through one connection) is not yet available everywhere, so it makes sense to bundle all assets into one big bundle and deliver all at once.
43 |
44 | In order to do this, we can use a bundler like Weback or Rollup. These bundlers are capable of tracing all dependencies starting from initial module and up to leaf ones and packing those modules together in a single bundle. Also if, configured they allow to minify the bundle using UglifyJS or any other compressor.
45 |
46 | Bud bundle approach also have caveats. Bundle may contain things that are not required to render particular requested page. Basically, client can download a huge initial bundle but in fact need only 30-40% of it.
47 |
48 | Modern bundlers allow to split the app into smaller chunks and progressively load them on demand when needed. In order to create a code split point we can use the dynamic import syntax:
49 |
50 | //B.js:
51 | import('./A.js').then(({noop}) => {
52 | noop();
53 | });
54 | Now the build tool can see that certain modules should not be included in initial chunk and can be loaded on demand. But on the other hand, if those chunks will be too granular we will return to the starting point with tons of small files.
55 |
56 | Unfortunately, if chunks will be less granular then, most likely they will have some modules included in more than one chunk. Those common modules (primarily the ones installed from NPM) could be moved to so-called common chunk. The goal is to find optimal proportion between initial bundle size, commons chunk size and size of code-splitted chunks.
57 |
58 | Introduction to React
59 | For this section we will create a simple React based project and learn how this library works and what are the core concepts.
60 |
61 | Let’s create an empty project folder and initialize NPM:
62 |
63 | $ mkdir learn-react
64 | $ cd learn-react
65 | $ npm init
66 | $ npm install react react-dom --save
67 | The quickest way to get started with React is to use package react-scripts.
68 |
69 | $ npm install react-scripts --save-dev
70 | Now let’s add a start script to package.json:
71 |
72 | {
73 | "scripts": {
74 | "start": "react-scripts start"
75 | }
76 | }
77 | NPM auto-binds CLI scripts installed to node_modules/.bin directory along with the packages, so we can use scripts directly.
78 |
79 | The smallest possible setup for React app is the following, we need a landing HTML page and one script with the app.
80 |
81 | Let’s start with bedrock HTML:
82 |
83 |
84 |
85 |
86 |
87 |
88 | Learn React
89 |
90 |
91 |
92 |
93 |
94 | And the main JS file:
95 |
96 | //src/index.js:
97 | import React from "react";
98 | import {render} from "react-dom";
99 | render(
100 |
It works!
,
101 | document.getElementById('app')
102 | );
103 | This is it. Now we can run the command to start development server:
104 |
105 | $ npm start
106 | It will open http://localhost:3000 and display It works! text.
107 |
108 | To learn more about React JSX, I encourage you to take a look at official documentation:
109 | https://reactjs.org/docs/introducing-jsx.html. This chapter will only briefly cover the main aspects that are essential for NextJS apps.
110 | The simplest React component is just a function that takes props as argument and returns JSX:
111 |
112 | const Cmp = (props) => (
{props.children}
);
113 | A more complicated components may have state:
114 |
115 | class Cmp extends React.Component {
116 | state = {value: 'init'};
117 | onClick = (event) => { this.setState({value: 'clicked'}); };
118 | render() {
119 | return (
120 |
121 | );
122 | }
123 | }
124 | Components may have static properties:
125 |
126 | class Cmp extends React.Component {
127 | static foo = 'foo';
128 | }
129 | or
130 |
131 | Cmp.foo = 'foo';
132 | These static properties are often used to describe some meta information about the components:
133 |
134 | import PropTypes from "prop-types";
135 | Cmp.propTypes = {
136 | propName: PropTypes.string
137 | };
138 | Next JS is actively using them and we will later show you how.
139 |
140 | The simplest way to achieve code splitting in React application is to store the entire progressively loaded component in state:
141 |
142 | class Cmp extends React.Component {
143 | state = {Sub: null};
144 | onClick = async (event) => {
145 | const Sub = (await import('./path/to/Sub.js')).default;
146 | this.setState({Sub});
147 | };
148 | render() {
149 | const {Sub} = this.state;
150 | return (
151 |
152 |
153 |
154 |
155 | );
156 | }
157 | }
158 | Another way to achieve the code splitting is to use the React Router.
159 |
160 | All React components have lifecycle hooks that can be utilized, for example, to load the data from remote server:
161 |
162 | class Cmp extends React.Component {
163 | state = {data: null};
164 | async componentWillMount() {
165 | const data = await (await fetch('https://example.com')).json();
166 | this.setState({data});
167 | }
168 | render() {
169 | const {data} = this.state;
170 | return (
171 |
172 | {JSON.stringify(data)}
173 |
174 | );
175 | }
176 | }
177 | React API has way more stuff, but these things are absolutely essential for Next JS.
178 |
179 | Why single page apps suffer performance issues?
180 | In order to start SPA has to download lots of assets to the client: JS files with the app itself, CSS files with styles, images, media and so on. It is impossible to develop a large scale JS app without any kind of modularization, so most JS apps consist of numerous small JS files (the above mentioned modules). CSS files also usually are separated by some criteria: per component, per page, etc.
181 |
182 | The nature of SPAs forces them to have heavy API traffic, basically, any user action that has to be persisted requires an API call. Pulling data from persistent storage also requires API calls.
183 |
184 | Both of these two aspects bring us to the most terrible SPA performance issue: the initial load time could be quite long. There were studies that clearly shown the correlation between the load time and page views, conversion and other vital metrics. On average customers leave the page if it fails to load within 2-3 seconds.
185 |
186 | Another big issue is SEO. Search engines tend to give higher rank to pages that load quicker. Plus, only recently crawlers learned how to parse and crawl SPAs properly.
187 |
188 | How to deal with it?
189 |
190 | Assume we have found a good balance between initial chunk and on demand chunks. We have applied compression and good cache strategies, but still, there is API layer that also has to be optimized for initial load.
191 |
192 | Potentially, we can combine all API requests in one huge request and load it. But different pages need different data, so we can’t create a request that will fit all, at least not within REST principles. Also, some of the data requires client side processing before we can make a subsequent request for more data. Modern API techniques like GraphQL allows
193 | to solve it more or less, and we will talk about it later in the book, but this still does not address the issue with not-so-smart search engine crawlers.
194 |
195 | Sad? Yes. But there is a solution for that. It is called server side rendering.
196 |
197 | Server side rendering
198 | Back in the days web servers used templates to deliver initial HTML to the client. Languages like Java, PHP, Python and Ruby were quite suitable for such kind of tasks. Those pages were called server-generated. Basically, all navigation and interaction was based on those dynamically generated pages.
199 |
200 | Those server generated pages were very simple in terms of user interaction, some hover effects and simple scripts. Some time after a more complicated scenarios were introduced and the bias moved towards client side. Servers began to generate not just full templates but also replaceable fragments to reflect more in-place changes. Later on because of the shift to REST APIs, a cleaner separation of concerns brought industry away from server-generated approaches to fully JS driven apps.
201 |
202 | But in order to more efficiently load the initial data for JS app, we can utilize this approach a little bit. We can render the initial markup on the server and then let JS app take over. The main assumption here is the fact that server side renderer is usually much closer to API server, ideally in same collocation, so it has much better connection and way more bandwidth than remote clients. It also can utilize all benefits of HTTP2 or any other protocols to maintain fast data exchange. The server side renderer is capable of doing all those chained requests much faster than clients, and all codebase can be pre-loaded and pre-parsed. Also it can use more aggressive data caching strategies because invalidation
203 | also could be centrally maintained.
204 |
205 | To decrease code duplication we would like to use same technology and same templates both on client and on the server.
206 | Such kind of apps is called universal or isomorphic.
207 |
208 | The general approach is as follows: we take NodeJS server, install a web framework and start listening to incoming requests. On every request that matches certain URL we take the client scripts and use them to bootstrap the initial state of the app for given page. Then we serialize the resulting HTML and data, bake it together and send to client.
209 |
210 | Client immediately shows the markup and then bootstraps the app on the client, applying initial data and state and hence taking control.
211 | Next page transition will happen purely on the client, it will load data from regular API endpoints just like before. One of the trickiest parts of this approach is to make sure that same page with same HTML will be rendered both on client and on the server, which means we need to make sure the client app will be bootstrapped in a certain state, that will result in same HTML.
212 |
213 | This brings us to framework choice. Not all client-side frameworks are capable of server-side rendering, for example it would be quite challenging to write a jQuery app that will pick up state and render itself correctly and on top of existing HTML.
214 |
215 | How to do server side rendering with React
216 | Luckily React is built with 2 main concepts in mind: it’s state driven and it is capable of rendering to plain HTML. React is often used with React Router, so let’s take this and explain how to render your React app on a server.
217 |
218 | React-based server side rendering frameworks, why Next JS
219 | Nowadays there are few competitors in React-based server side rendering market. We can divide them into the following categories:
220 |
221 | Drop in dynamic solution (NextJS, Electrode)
222 | Drop in static solution (Gatsby, React Static)
223 | Custom solutions
224 | The main difference between first two approaches is the way it builds and serves the app from server.
225 |
226 | Static solution makes a static HTML build (with all possible router pages) and then this build can be served by static server like Nginx, or any other. All HTML is pre-bakes, as well as initial state. This is very suitable for websites with incremental content updates that happen infrequently, for example, a blog.
227 |
228 | Dynamic solution generates HTML on the fly every time when client requests it. This means we can put any dynamic logic, any dynamic HTML blocks like per-request ads and so on. But the drawback is that it requires a long running server.
229 | This server has to be monitored and ideally should become a cluster of servers for redundancy to make sure of its’ high availability.
230 |
231 | We will put main focus of this book on dynamic solutions as they are more flexible and more complex and so require deeper understanding.
232 |
233 | For better understanding lets dive deeper in custom solution using only React and React Router.
234 |
235 | Let's install the router and special package to configure routes statically (it's impossible to generate purely dynamic routes on server):
236 |
237 | npm i --save react-router-dom react-router-config
238 | Now let's configure the routes:
239 |
240 | const routes = [
241 | {
242 | path: '/',
243 | exact: true,
244 | component: Index
245 | },
246 | {
247 | path: '/list',
248 | component: List
249 | }
250 | ];
251 | export default routes;
252 | The main app entry point should then look like this:
253 |
254 | // index.js
255 | import React from 'react';
256 | import {render} from 'react-dom';
257 | import BrowserRouter from 'react-router-dom/BrowserRouter';
258 | import { renderRoutes } from 'react-router-config';
259 | import routes from './routes';
260 |
261 | const Router = () => {
262 | return (
263 |
264 | {renderRoutes(routes)}
265 |
266 | )
267 | };
268 |
269 | render(, document.getElementById('app'));
270 | On server it will be the following:
271 |
272 | import express from 'express';
273 | import React from 'react';
274 | import { renderToString } from 'react-dom/server';
275 | import StaticRouter from 'react-router-dom/StaticRouter';
276 | import { renderRoutes } from 'react-router-config';
277 | import routes from './src/routes';
278 |
279 | const app = express();
280 |
281 | app.get('*', (req, res) => {
282 | let context = {}; // pre-fill somehow
283 | const content = renderToString(
284 |
285 | {renderRoutes(routes)}
286 |
287 | );
288 | res.render('index', {title: 'SSR', content });
289 | });
290 | But this will simply render the page with no data. In order to pre-populate data into the page we need to do the following, both in component and in server:
291 |
292 | Each data-enabled component must expose a method that the server should call during route resolution
293 | Server iterates over all matched components and utilizes exposed methods
294 | Server collects the data and puts in some storage
295 | Server renders the HTML using routes and data from storage
296 | Server sends to the client the resulting HTML along with data
297 | Client initializes using HTML and pre-populates state using data
298 | We purposely won't show steps 3 and more because there is no generic way for pure React and React Router. For storage most solutions will use Redux and this is a whole another topic. So here we just show the basic principle.
299 |
300 | // list.js
301 | import React from "react";
302 |
303 | const getText = async () => (await (await fetch('https://api.github.com/users/octocat')).text());
304 |
305 | export default class List extends React.Component {
306 |
307 | state = {text: ''};
308 |
309 | static async getInitialProps(context) {
310 | context.text = await getText();
311 | }
312 |
313 | async componentWillMount() {
314 | const text = await getText();
315 | this.setState({text})
316 | }
317 |
318 | render() {
319 | const {staticContext} = this.props;
320 | let {text} = this.state;
321 | if (staticContext && !text) text = staticContext.text;
322 | return (
323 |
Text: {text}
324 | );
325 | }
326 |
327 | }
328 | // server.js
329 | // all from above
330 | app.get('*', (req, res) => {
331 | const {url} = req;
332 | const matches = matchRoutes(routes, url);
333 | const context = {};
334 | const promises = matches.map(({route}) => {
335 | const getInitialProps = route.component.getInitialProps;
336 | return getInitialProps ? getInitialProps(context) : Promise.resolve(null)
337 | });
338 | return Promise.all(promises).then(() => {
339 | console.log('Context', context);
340 | const content = renderToString(
341 |
342 | {renderRoutes(routes)}
343 |
344 | );
345 | res.render('index', {title: 'SSR', content});
346 | });
347 | });
348 | The reason why we are not covering those aspects is because even after tons of research it becomes obvious that custom solution will always have quirks and glitches primarily because React Router was not meant to be used on a server, so
349 | every custom solution is basically a bunch of hacks. It would be much better to take a stable solution which was built with Server Side Rendering in mind from day one.
350 |
351 | Among other competitors NextJS stands out as one of the pioneers of the approach, this framework is so far the most popular these days. Primarily because it offers a very convenient API, easy installation, zero config and huge community. Unlike Electrode which is extremely painful to configure.
352 |
353 | Full comparison is available in my article https://medium.com/disdj/solutions-for-react-app-development-f9fcaeba504.
354 |
355 | Summary
356 | In this chapter we have learned how web apps evolved over time from simple server generated pages to single page apps and then back to server generated pages with SPAs on top. We learned what is React JS and how to server-render the React application.
357 |
358 | In the next chapter we will use this knowledge to build a more advanced application that still follows these core principles.
--------------------------------------------------------------------------------
/book/2-basics.md:
--------------------------------------------------------------------------------
1 | In this chapter we will learn very basics of NextJS, like installation, development and production usage, how to create simple pages and add components to them, how to apply styles and add media.
2 |
3 | Installation
4 | Developer mode
5 | Pages
6 | Production mode
7 | Routing
8 | Dynamic routing
9 | SEO-friendly routing
10 | Styles
11 | Media
12 | Graphs
13 | Installation of NextJS
14 | First, create an empty project folder and initialize NPM in it:
15 | $ mkdir next-js-condensed
16 | $ cd next-js-condensed
17 | $ npm init
18 | After that let’s install the NextJS package:
19 | $ npm install nextjs@latest --save-dev
20 | $ npm install react@latest react-dom@latest --save
21 | The reason why we save NextJS to dev dependencies is to clearly separate deps for client and for server. Server-side deps will be in dev, client will be in regular.
22 |
23 | If you're using Git or any other source control it makes sense to add an ignore file that will remove build artifacts folder from the source control. We will show an example .gitignore file here:
24 |
25 | Running Next JS in developer mode
26 | In order to start the server by convention we need to define a start script in package.json so we will add the following there:
27 |
28 | {
29 | "scripts": {
30 | "start": "next"
31 | }
32 | }
33 | Now you can start the server by typing this in console:
34 |
35 | $ npm start
36 | Now if you visit http://localhost:3000 in your browser you will see the running server.
37 |
38 | Creating your first Next JS page
39 | Now let's create the first page and place it in pages folder:
40 |
41 | // pages/index.js
42 | import React from "react";
43 | export default () => (
Hello, World!
);
44 | Now, if you run the dev server (npm start) and visit http://localhost:3000 you will see the page.
45 |
46 |
47 | Now let's see how NextJS handle errors in your files:
48 |
49 | // pages/index.js
50 | import React from "react";
51 | export default () => (
Hello, World!
);
52 | // ^ here we purposely not closing this tag
53 | then reload the page and see this:
54 |
55 |
56 | Running Next JS production build
57 | Next JS supports two kinds of production usage: static and dynamic, the main difference is that static build can be served by any static HTTP server as a static web site, whereas dynamic usage means that there will be a NextJS server that executes production build.
58 |
59 | Static mode is best suitable for simple websites with no dynamic content. We need to add a script to package.json:
60 |
61 | {
62 | "scripts": {
63 | "static": "next export"
64 | }
65 | }
66 | Now if we run
67 |
68 | $ npm run static
69 | It will create a static build that we can deploy somewhere. We will cover this in later chapters.
70 |
71 | In order to build & run the site for dynamic production mode we will add more scripts to package.json:
72 |
73 | {
74 | "scripts": {
75 | "build": "next build",
76 | "server": "next start"
77 | }
78 | }
79 | Then in console run
80 |
81 | $ npm run build
82 | $ npm run server
83 | This will make the build and run the production server using that build.
84 |
85 | Making Next JS Routing
86 | Let's make another page:
87 |
88 | // pages/second.js
89 | import React from "react";
90 | export default () => (
208 | );
209 | Here we imported the data source and iterated over it to produce some simple nav links, as you see, for convenience we may also use href as URL object, NextJS will serialize it into a standard string.
210 |
211 | Now, let's update the second page:
212 |
213 | // pages/second.js
214 | import React from 'react';
215 | import Nav from "../components/Nav";
216 | import posts from "../data/posts";
217 |
218 | export default ({url: {query: {id}}}) => (
219 |
220 |
221 |
222 |
{posts[id].title}
223 |
224 | );
225 | Now if we visit http://localhost:3000 we will see a clickable list of posts, each of them leads to a dedicated dynamic page.
226 |
227 | Unfortunately, now if we visit second page directly from our Nav component (by clicking a top menu button) we will get a nasty error. Let's make it prettier. We should import a special NextJS Error component and return it in case of any errors:
228 |
229 | // pages/second.js
230 | import React from 'react';
231 | import Error from 'next/error';
232 | import Nav from "../components/Nav";
233 | import posts from "../data/posts";
234 |
235 | export default ({url: {query: {id}}}) => (
236 | (posts[id]) ? (
237 |
238 |
239 |
240 |
{posts[id].title}
241 |
242 | ) : (
243 |
244 | )
245 | );
246 | We have added an import:
247 |
248 | import Error from 'next/error';
249 | And wrapped the component in a ternary operator:
250 |
251 | export default ({url: {query: {id}}}) => (
252 | (posts[id]) ? (...) : ()
253 | );
254 | This will return a NextJS nice 404 Page not found error page.
255 |
256 | Making Next JS Routing masks: SEO-friendly URLs
257 | If you look at the location bar of the browser when you visit the second page, you'll see something like http://localhost:3000/second?id=0, which is kinda fine, but not pretty enough. We can add some niceness to the URL schema that we use. This is optional, but it's always good to have SEO-friendly URLs instead of Query-String based stuff.
258 |
259 | In order to do that we should use a special as prop of Link component:
260 |
261 |
262 | {post.title}
263 |
264 | But if you visit such link and reload the page you will see 404. Why is that? It's because URL masking (a technology we just used) works on client side in runtime, and when we reload the page we need to teach server to work with such URLs.
265 |
266 | In order to do that we will have to make a custom server. Luckily, NextJS offers useful tools to simplify it.
267 |
268 | Let's start with installing Express:
269 |
270 | $ npm install --save-dev express
271 | The server code should look like this:
272 |
273 | // /server.js
274 | const express = require('express');
275 | const next = require('next');
276 |
277 | const port = 3000;
278 | // use default NodeJS environment variable to figure out dev mode
279 | const dev = process.env.NODE_ENV !== 'production';
280 | const app = next({dev});
281 | const handle = app.getRequestHandler();
282 | const server = express();
283 |
284 | server.get('/post/:id', (req, res) => {
285 | const actualPage = '/second';
286 | const queryParams = {id: req.params.id};
287 | app.render(req, res, actualPage, queryParams);
288 | });
289 |
290 | server.get('*', (req, res) => { // pass through everything to NextJS
291 | return handle(req, res)
292 | });
293 |
294 | app.prepare().then(() => {
295 |
296 | server.listen(port, (err) => {
297 | if (err) throw err
298 | console.log('NextJS is ready on http://localhost:' + port);
299 | });
300 |
301 | }).catch(e => {
302 |
303 | console.error(e.stack);
304 | process.exit(1);
305 |
306 | });
307 | The main thing in this code is the following code block:
308 |
309 | server.get('/post/:id', (req, res) => {
310 | const actualPage = '/second';
311 | const queryParams = {id: req.params.id};
312 | app.render(req, res, actualPage, queryParams);
313 | });
314 | It uses URL parser to figure out URL param and provide it to actual page as a query string param, that is understandable by NextJS server side renderer.
315 |
316 | In order to launch this as usual we need to alter package.json's scripts section:
317 |
318 | {
319 | "scripts": {
320 | "start": "node server.js"
321 | }
322 | }
323 | Now if we run
324 |
325 | $ npm start
326 | As we did before and directly open a post: http://localhost:3000/post/0 it will work as expected.
327 |
328 | Adding styles to application, what is CSS in JS
329 | There are many ways how NextJS app can be styled.
330 |
331 | Simplest way is to use inline styles. Obviously this is the worst possible way, but we'll start small.
332 |
333 | const selectedStyles = {
334 | fontWeight: 'bold'
335 | };
336 |
337 | const regularStyles = {
338 | fontWeight: 'normal'
339 | };
340 |
341 | const Btn = ({href, onClick, children, pathname}) => (
342 |
345 | );
346 | Obviously this does not scale at all. Luckily, NextJS offers a technique called JSS aka CSS in JS.
347 |
348 | // components/button.js
349 | import React from 'react';
350 | import {withRouter} from 'next/router';
351 |
352 | export default withRouter(({href, onClick, children, router}) => (
353 |
354 |
358 |
373 |
374 | ));
375 | This will create a scoped stylesheet. If you want a global one you should use