5 | );
6 |
7 | export default Index;
--------------------------------------------------------------------------------
/00-hello-next/readme.md:
--------------------------------------------------------------------------------
1 | # Hello nextjs
2 |
3 | Let's get started with the very basics let's create a very basic 'Hello World' sample using next.
4 |
5 | In this example we will use javascript as base language, in our next sample we will move to typescript.
6 |
7 | This sample is quite similar to the basic one pointed in nextjs tutorial: https://nextjs.org/learn/basics/getting-started/setup
8 |
9 | # Steps
10 |
11 | - Let's create our project (ensure that your folder container does not contain spaces or capital letters).
12 |
13 | ```bash
14 | npm init -y
15 | ```
16 |
17 | - Let's start by installing some basic packages:
18 |
19 | ```bash
20 | npm install react react-dom next --save
21 | ```
22 |
23 | - Let's open _package.json_ and add the following npm script:
24 |
25 | _package.json_
26 |
27 | ```diff
28 | "scripts": {
29 | - "test": "echo \"Error: no test specified\" && exit 1"
30 | + "dev": "next"
31 | },
32 | ```
33 |
34 | - Let's create our first page (by convention it should fall under the pages subfolder).
35 |
36 | _./pages/index.js_
37 |
38 | ```javascript
39 | const Index = () => (
40 |
41 |
Hello Next.js
42 |
43 | );
44 |
45 | export default Index;
46 | ```
47 |
48 | - Let's run the sample:
49 |
50 | ```bash
51 | npm run dev
52 | ```
53 |
54 | - Open your web browser and navigate to _http://localhost:3000_
55 |
--------------------------------------------------------------------------------
/00-hello-next/readme_es.md:
--------------------------------------------------------------------------------
1 | # Hello nextjs
2 |
3 | Empecemos con lo más simple, vamos a crear un ejemplo muy sencillo de 'Hello World' utilizando next.
4 |
5 | En este ejemplo utilizaremos javaScript como lenguaje base, en los siguientes ejemplo cambiaremos a typescript.
6 |
7 | Este ejemplo es bastante parecido al que indican en el tutorial de nextjs:
8 | https://nextjs.org/learn/basics/getting-started/setup
9 |
10 | # Pasos
11 |
12 | - Vamos a crear nuestro projecto (asegurate de que tu carpeta contenedora no incluye espacios o mayusculas)
13 |
14 | ```bash
15 | npm init -y
16 | ```
17 |
18 | - Comenzamos instalando algunos paquetes básicos:
19 |
20 | ```bash
21 | npm install react react-dom next --save
22 | ```
23 |
24 | - Abrimos _package.json_ y le añadimos el siguiente comando npm:
25 |
26 | _package.json_
27 |
28 | ```diff
29 | "scripts": {
30 | - "test": "echo \"Error: no test specified\" && exit 1"
31 | + "dev": "next"
32 | },
33 | ```
34 |
35 | - Creamos nuestra primera página (por convención debe incluirse bajo el subdirectorio de _pages_)
36 |
37 | _./pages/index.js_
38 |
39 | ```javascript
40 | const Index = () => (
41 |
5 | )
6 |
7 | export default UserInfoPage;
8 |
--------------------------------------------------------------------------------
/02-navigation/readme.md:
--------------------------------------------------------------------------------
1 | # Navigation
2 |
3 | So far we have created a simple porject including a single page, let's create a second page and add navigation between the first
4 | and the second one.
5 |
6 | # Steps
7 |
8 | - To get started copy the files from sample _01-hello-typescript_ and execute:
9 |
10 | ```bash
11 | npm install
12 | ```
13 |
14 | - Let's create a page called 'user-info'.
15 |
16 | _./pages/user-info.tsx_
17 |
18 | ```typescript
19 | const UserInfoPage = () => (
20 |
21 |
I'm the user infopage
22 |
23 | )
24 |
25 | export default UserInfoPage;
26 |
27 | ```
28 |
29 | - Now in order to add some useful navigation in both client and server side we can wrap a navigation ancho with a nextjs hoc to handle navigatin in an universal way.
30 |
31 | _./pages/index.tsx_
32 |
33 | ```diff
34 | + import Link from 'next/link';
35 |
36 | const myLanguage: string = "Typescript";
37 |
38 | const Index = () => (
39 |
46 | );
47 |
48 | export default Index;
49 | ```
50 |
51 | > If we don't use this _Link_ HOC when clicking on the link it would navigate to the server rather than making client side
52 | navigation.
53 |
54 | - Let's run the sample:
55 |
56 | ```bash
57 | npm run dev
58 | ```
59 |
60 | > Now you can notice, navigation is done client side, plus browser history is informed on each navigation.
61 | > See differences using `a` element.
62 |
--------------------------------------------------------------------------------
/02-navigation/readme_es.md:
--------------------------------------------------------------------------------
1 | # Navegacion
2 |
3 | Hasta aquí hemos creado un simple proyecto qeu incluye una página simple, vamos a crear una segunda página y añadir navegación entre la primera y la segunda
4 |
5 | #Pasos
6 |
7 | - Para empezar debemos copiar los ficheros del ejemplo _01-hello-typescript_ y ejecutar:
8 |
9 | ```bash
10 | npm install
11 | ```
12 |
13 | - Ahora creamos una nueva página y la llamamos 'user-info':
14 | _./pages/user-info.tsx_
15 |
16 | ```typescript
17 | const UserInfoPage = () => (
18 |
19 |
I'm the user infopage
20 |
21 | )
22 |
23 | export default UserInfoPage;
24 |
25 | ```
26 |
27 | - Ahora para poder añadir una navegación satisfactoria tanto en el cliente como en el servidor debemos envolver la navegación mediante nextjs para poder manejar la navegación de forma global.
28 |
29 |
30 | _./pages/index.tsx_
31 |
32 | ```diff
33 | + import Link from 'next/link';
34 |
35 | const myLanguage: string = "Typescript";
36 |
37 | const Index = () => (
38 |
85 | );
86 |
87 | + Index.getInitialProps = async () => {
88 | + const users = await fetchUsers();
89 | + console.log(users);
90 |
91 | + return {
92 | + users,
93 | + }
94 | + }
95 |
96 | export default Index;
97 | ```
98 |
99 | > Now if we place a _console.log(data);_ right after
100 | > _fetch_ we can check that the output is displayed not in the browser but in your server console.
101 | > But if we navigate to `user-info` and back to index page, it will execute in client side.
102 |
103 | - Now that we have the data let's add the UI part.
104 |
105 | _./components/users/header.tsx_
106 |
107 | ```typescript
108 | export const Header = () => (
109 |
81 | );
82 |
83 | + Index.getInitialProps = async () => {
84 | + const users = await fetchUsers();
85 | + console.log(users);
86 |
87 | + return {
88 | + users,
89 | + }
90 | + }
91 |
92 | export default Index;
93 | ```
94 |
95 | > Ahora si colocamos un _console.log(data);_ justo después de
96 | > _fetch_ aunque no se vea en el navegador el resultado se mostrará en la consola del servidor.
97 | > Pero si navegamos a `user-info` y después volvemos, veremos que se ejecuta en cliente.
98 |
99 | - Ahora que tenemos la parte del consumo de datos vamos a comenzar con la parte UI.
100 |
101 | _./components/users/header.tsx_
102 |
103 | ```typescript
104 | export const Header = () => (
105 |
8 | ));
9 |
10 | export default UserInfoPage;
11 |
--------------------------------------------------------------------------------
/04-querystring/readme.md:
--------------------------------------------------------------------------------
1 | # Query string navigation
2 |
3 | We have created a page to display a list of users, what about displaying info of a given user in a detail page?
4 | (use case: user click on a user in the given list, navigate to detail page to display the details of that user).
5 |
6 | # Steps
7 |
8 | - We will start form sample _03-fetch_, let's copy the content into a new folder (we will work from there).
9 |
10 | - Let's install the dependencies.
11 |
12 | ```bash
13 | npm install
14 | ```
15 |
16 | - Let's update _row.tsx_ and add a link that will let us navigate to the detail page.
17 |
18 | - Let's first add a _link_ import.
19 |
20 | _./pages/components/users/row.tsx_
21 |
22 | ```diff
23 | import * as Next from 'next';
24 | + import Link from 'next/link';
25 | import { User } from '../../model/user';
26 | ...
27 |
28 | ```
29 |
30 | - Now lets add the link:
31 |
32 | _./pages/components/users/row.tsx_
33 |
34 | ```diff
35 |
105 | ```
106 |
107 | - And later to see other parameter captured from the query string add this:
108 |
109 | _./pages/user-info.tsx_
110 |
111 | ```diff
112 | import { withRouter } from 'next/router';
113 |
114 | const UserInfoPage = withRouter((props) => (
115 |
116 |
I'm the user infopage
117 | +
{props.router.query.id}
118 |
{props.router.query.login}
119 |
120 | ));
121 |
122 | export default UserInfoPage;
123 | ```
124 |
125 | - Let's run the sample again.
126 |
127 | ```bash
128 | npm run dev
129 | ```
130 |
--------------------------------------------------------------------------------
/04-querystring/readme_es.md:
--------------------------------------------------------------------------------
1 | # Navegación por "Query String"
2 |
3 | Hemos creado una página para listar los usuarios, que tal si mostramos la información de un usuario dado en una página de detalles?
4 |
5 | (Caso de uso: Un usuario hace click en la lista de usuarios, navega a la página de detalles para ver los detalles de ese usuario).
6 |
7 | # Pasos
8 |
9 | - Comenzamos por el ejemplo _03-fetch_, copiamos el contenido dentro de una carpeta nueva y trabajamos desde allí.
10 |
11 | -Instalamos las dependencias
12 |
13 | ```bash
14 | npm install
15 | ```
16 |
17 | - Actualizamos _row.tsx_ y añadimos un link que nos permita navegar a la página de detalles.
18 |
19 | - Primero añadimos un import del _"link"_ .
20 |
21 | _./pages/components/users/row.tsx_
22 |
23 | ```diff
24 | import * as Next from 'next';
25 | + import Link from 'next/link';
26 | import { User } from '../../model/user';
27 | ...
28 |
29 | ```
30 |
31 | - Ahora añadimos el elemento Link:
32 |
33 | _./pages/components/users/row.tsx_
34 |
35 | ```diff
36 |
8 | ));
9 |
10 | export default UserInfoPage;
11 |
--------------------------------------------------------------------------------
/05-friendly-url/readme.md:
--------------------------------------------------------------------------------
1 | # Friendly URL's
2 |
3 | One of the goals of supporting server side rendering is to obtain good SEO results. One of the basic pilars of SEO consists on generating
4 | friendly URL's, right now in the user detail page we are generating something like:
5 |
6 | http://localhost:3000/user-info?login=brauliodiez
7 |
8 | It would be better to rewrite the URL and display it in the following way:
9 |
10 | http://localhost:3000/user-info/login/brauliodiez
11 |
12 | We will do that in two steps:
13 | - Support this friendly url on the client side.
14 | - Support this friendly url on the server side.
15 |
16 | # Steps
17 |
18 | - Let's start by copying the content of _04-querystring_ in our working folder.
19 |
20 | - Let's install the needed packages.
21 |
22 | ```bash
23 | npm install
24 | ```
25 |
26 | - Let's update our _row.tsx_ component to use a link alias.
27 |
28 | _./pages/components/users/row.tsx_
29 |
30 | ```diff
31 |
37 | ...
38 |
39 | ```
40 |
41 | - Let's run the sample and check how it works:
42 |
43 | ```bash
44 | npm run dev
45 | ```
46 |
47 | - Are we ready? The answer is no, if we click on refresh it won't perform the server side rendering properly (we get a 404).
48 |
49 | - To get the server behaving in the same way as the client we need to do some extra plumbing.
50 |
51 | - Let's install _express_
52 |
53 | ```bash
54 | npm install express --save
55 | ```
56 |
57 | - Let's create a file called _server.js_
58 |
59 | _./server.js_
60 |
61 | ```javascript
62 | const express = require('express');
63 | const next = require('next');
64 |
65 | const dev = process.env.NODE_ENV !== 'production';
66 | const app = next({ dev });
67 | const handler = app.getRequestHandler();
68 |
69 | app
70 | .prepare()
71 | .then(() => {
72 | const server = express();
73 |
74 | server.get('*', (req, res) => {
75 | return handler(req, res);
76 | });
77 |
78 | server.listen(3000, err => {
79 | if (err) throw err;
80 | console.log('> Ready on http://localhost:3000');
81 | });
82 | })
83 | .catch(ex => {
84 | console.error(ex.stack);
85 | process.exit(1);
86 | });
87 |
88 | ```
89 |
90 | > in this file we just create a next app and listen to any request, this request will just be handled by the next app.
91 |
92 | - Let's update our _package.json_ entry.
93 |
94 | _./package.json_
95 |
96 | ```diff
97 | "scripts": {
98 | - "dev": "next"
99 | + "dev": "node server.js"
100 | },
101 | ```
102 |
103 | - Let's double check that the server is working (no server side clean url yet).
104 |
105 | ```bash
106 | npm run dev
107 | ```
108 |
109 | - Now let's add a get case for the new friendly url we have created.
110 |
111 | _./server.js_
112 |
113 | ```diff
114 | ...
115 |
116 | app
117 | .prepare()
118 | .then(() => {
119 | const server = express();
120 |
121 | + server.get('/user-info/login/:login', (req, res) => {
122 | + return app.render(req, res, '/user-info', { login: req.params.login });
123 | + });
124 |
125 | server.get('*', (req, res) => {
126 | return handler(req, res);
127 | });
128 | ...
129 |
130 | ```
131 |
132 | > NOTE: Important, you have to declare this route before `*`.
133 |
134 | - If we run the code now, we're going to get the right behavior after we refresh the page.
135 |
136 | ```bash
137 | npm run dev
138 | ```
139 |
--------------------------------------------------------------------------------
/05-friendly-url/readme_es.md:
--------------------------------------------------------------------------------
1 | # URLS amigables
2 |
3 | Uno de los objetivos que tenemos a la hora de implementar server side rendering es poder obtener buenos resultados en SEO. Uno de sus pilares es generar URLS amigables. Si vamos al "user detail page" podremos ver que se esta generando algo como:
4 |
5 | http://localhost:3000/user-info?login=brauliodiez
6 |
7 | De otra manera se podría reescribir la URL mostrándola así:
8 |
9 | http://localhost:3000/user-info/login/brauliodiez
10 |
11 | Podemos hacer esto en dos pasos:
12 | - Dar soporte a la URL amigable en el lado del cliente.
13 | - Dar soporte a la URL amigable en el lado del servidor.
14 |
15 | # Pasos
16 |
17 | - Vamos a copiar el contenido de _04-querystring_ en nuestra carpeta de trabajo.
18 |
19 | - Acto seguido, instalaremos los paquetes necesarios.
20 |
21 | ```bash
22 | npm install
23 | ```
24 | - Actualizaremos el componente _row.tsx_ para usar un alias en nuestro enlace.
25 |
26 | _./pages/components/users/row.tsx_
27 |
28 | ```diff
29 |
19 | );
20 |
21 | UserInfoPage.getInitialProps = async props => {
22 | const login = props.query.login as string;
23 | const userDetail = await fetchUserDetail(login);
24 |
25 | return {
26 | login,
27 | userDetail,
28 | };
29 | };
30 |
31 | export default UserInfoPage;
32 |
--------------------------------------------------------------------------------
/07-styles/readme.md:
--------------------------------------------------------------------------------
1 | # Adding styles with Next.js and CSS
2 |
3 | In this sample, we are going to give a brief overview on the steps needed to add CSS support to our Next.js based project.
4 |
5 | # Steps to add standard global CSS
6 |
7 | - We will take sample 06-detail-page as our starting point
8 |
9 | - As usual, let's start with the installation of the dependencies in the package.json file.
10 |
11 | ```bash
12 | npm install
13 | ```
14 |
15 | - In order to use external CSS files in our application, we actually need to add a new dependency to our project. Concretely, we should run the code below to add the ```next-css``` package to our app:
16 |
17 | ```bash
18 | npm install @zeit/next-css --save
19 | ```
20 |
21 | - This new dependency provides us with a ```withCSS``` function that we can invoke in our _./next.config.js_ file to extend our current configuration with CSS-handling capabilities. Thus, should refactor our _./next.config.js_ as follows:
22 |
23 | _./next.config.js_
24 |
25 | ```diff
26 | const withTypescript = require('@zeit/next-typescript');
27 | + const withCSS = require('@zeit/next-css');
28 |
29 | - module.exports = withTypescript();
30 | + module.exports = withTypescript(withCSS());
31 |
32 | ```
33 |
34 | - And that's it, we can now handle CSS files. So, let's go an create a global CSS styles file for our app. For the time being, let's create a ```global-styles.css```:
35 |
36 | _./styles/global-styles.css_
37 |
38 | ```
39 | .blue-box {
40 | border: 3px solid blue;
41 | }
42 | ```
43 |
44 | - And now let's add this new style to our user collection. For example, let's go to our user-collection header component and apply this new CSS rule on the 'Avatar' header. Like this:
45 |
46 | _./components/users/header.tsx_
47 |
48 | ```diff
49 | + import '../../styles/global-styles.css';
50 |
51 | export const Header = () => (
52 |
53 | -
Avatar
54 | +
Avatar
55 |
Id
56 |
Name
57 |
58 | )
59 |
60 | ```
61 |
62 | - If we run our app now... we should actually see no changes at all. How's that? The problem is that we have not yet told Next.js how we should be loading our global CSS file in our webapp. By default, ```next-css``` is compiling our CSS files to _.next/static/style.css_, and the contents of this file are being served from the URL below
63 |
64 | ```
65 | /_next/static/css/style.chunk.css
66 | ```
67 |
68 | - Thus, we need to tell our app that the CSS should be read from that entry point. In order to do this, we need to somehow tell Next.js to use a custom document structure, so that we can provide a suitable ```link``` inside the HTML document's header that points to our CSS serving point. We can do this by adding a filed named ```_document.js``` (care with the preceding underscore!) into our ```pages``` folder. The contents of such a file would be as read below:
69 |
70 | _./pages/\_document.js_
71 |
72 | ```javascript
73 | import Document, { Head, Main, NextScript } from 'next/document';
74 |
75 | export default class MyDocument extends Document {
76 | render() {
77 | return (
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | ```
92 |
93 | - After adding this custom document specification, we can now run ```npm run dev```, and we shall see that the 'Avatar' block of our user collection header is indeed wrapped in a blue box.
94 |
95 | # Steps to add CSS using the CSS-Modules approach
96 |
97 | - If we want to process the CSS files in our application following the pattern for CSS Modules, we need to enable the corresponding flag in our next.js config file.
98 |
99 | _./next.config.js_
100 |
101 | ```diff
102 | const withTypescript = require('@zeit/next-typescript');
103 | const withCSS = require('@zeit/next-css');
104 |
105 | - module.exports = withTypescript(withCSS());
106 | + module.exports = withTypescript(
107 | + withCSS({
108 | + cssModules: true,
109 | + cssLoaderOptions: {
110 | + camelCase: true,
111 | + },
112 | + })
113 | + );
114 |
115 | ```
116 |
117 | - By adding this, we can now go to our users folder and create a new _./components/users/header.css_ file with some extra styling rules for our table's header. For instance:
118 |
119 | _./components/users/header.css_
120 |
121 | ```css
122 | .purple-box {
123 | border: 2px dotted purple;
124 | }
125 | ```
126 |
127 | - Now we can import this file into our header component code and use the class we have just defined to modify, for example, the styles for Id header. Notice that since we used kebab case notation for our naming convention, we need to active `camelCase` flag to use it in javascript files:
128 |
129 | _./components/users/header.tsx_
130 |
131 | ```diff
132 | import '../../styles/global-styles.css';
133 | + const styles = require('./header.css');
134 |
135 | export const Header = () => (
136 |
137 |
Avatar
138 | -
Id
139 | +
Id
140 |
Name
141 |
142 | );
143 |
144 | ```
145 |
146 | - Let's re-run our app. We can see that the Id header is in fact wrapped in a dotted purple box. Moreover, the blue-box around the Avatar header has gone the way of the dodos! This is because every CSS is being compiled now as if using the CSS-Modules pattern, so we cannot add them to our component classes by just adding the corresponding import and the literal name of the class. In fact, if we check the Sources in the browser's console, we can check that the contents for the ```_next/static/style.css``` file do indeed include both of our CSS rules. However, the blue box is not being indexed properly and thus not rendered.
147 |
148 | - Let's refactor our code a little further to fully follow CSS-Modules pattern. Delete the previously created _./styles_ folder and move the CSS rules for the blue box into our _./components/users/header.css_ file. Let's also remove the dash in the name for the CSS class.
149 |
150 | _./styles/global-styles.css_
151 |
152 | ```diff
153 | - .blue-box {
154 | - border: 3px solid blue;
155 | - }
156 |
157 | ```
158 |
159 | _./components/users/header.css_
160 |
161 | ```diff
162 | .purple-box {
163 | border: 2px dotted purple;
164 | }
165 |
166 | +.blue-box {
167 | + border: 3px solid blue;
168 | +}
169 | ```
170 |
171 | - And finally, let's refactor the way we are referring to the blue box class in our header component:
172 |
173 | _./components/users/header.tsx_
174 |
175 | ```diff
176 | - import '../../styles/global-styles.css';
177 | const styles = require('./header.css');
178 |
179 | export const Header = () => (
180 |
181 | -
Avatar
182 | +
Avatar
183 |
Id
184 |
Name
185 |
186 | );
187 |
188 | ```
189 |
190 | - We should now be able to see both styles being applied correctly
191 |
192 | - Just to finish, the ```withCSS``` function is actually a compositional interface to provide syntactic sugar over your regular webpack configuration. In fact, it is using a ```css-loader``` behind the scenes. We can provide additional configuration options as per [webpack's css loader options](https://github.com/webpack-contrib/css-loader#options). Let's add, for example, local scope to our CSS rules.
193 |
194 | _./next.config.js_
195 |
196 | ```diff
197 | const withTypescript = require('@zeit/next-typescript');
198 | const withCSS = require('@zeit/next-css');
199 |
200 | module.exports = withTypescript(
201 | withCSS({
202 | cssModules: true,
203 | cssLoaderOptions: {
204 | camelCase: true,
205 | + importLoaders: 1,
206 | + localIdentName: '[local]___[hash:base64:5]',
207 | },
208 | })
209 | );
210 |
211 | ```
212 |
213 | - We would be naming our CSS rules using the local names in the CSS file alongside a hash. If we go now to the browser's console and check the names for the CSS rules previously stored in ```_next/static/css/styles.chunk.css```, we can see that they are no longer named after just a rather long hash, but instead they now follow the pattern established by ```cssLoaderOptions.localIdentName``` configuration property.
214 |
215 | - Since we are using `css-modules`, we don't need the `_document.js` page to load css files. So, we could remove it.
216 |
217 | > NOTE: Check fetching styles starting on `user-info` page.
218 |
219 | ## Appendix
220 |
221 | - To finish this sample, let's use CSS rules to provide some actually good looking styles to the users table, shall we?:
222 |
223 | _./components/users/header.css_
224 |
225 | ```diff
226 | - .purple-box {
227 | - border: 2px dotted purple;
228 | - }
229 |
230 | - .blue-box {
231 | - border: 3px solid blue;
232 | - }
233 |
234 | + .header {
235 | + background-color: #ddefef;
236 | + border: solid 1px #ddeeee;
237 | + color: #336b6b;
238 | + padding: 10px;
239 | + text-align: left;
240 | + text-shadow: 1px 1px 1px #fff;
241 | + }
242 |
243 | ```
244 |
245 | - And update `row` and `table`:
246 |
247 | _./components/users/row.css_
248 |
249 | ```css
250 | .row {
251 | border: solid 1px #DDEEEE;
252 | color: #333;
253 | padding: 10px;
254 | text-shadow: 1px 1px 1px #fff;
255 | }
256 |
257 | ```
258 |
259 | _./components/users/table.css_
260 |
261 | ```css
262 | .table {
263 | border: solid 1px #ddeeee;
264 | border-collapse: collapse;
265 | border-spacing: 0;
266 | font: normal 13px Arial, sans-serif;
267 | }
268 |
269 | ```
270 |
271 | - And finally, we modify the corresponding components to add the new styling rules:
272 |
273 | _./components/users/header.tsx_
274 |
275 | ```diff
276 | const styles = require('./header.css');
277 |
278 | export const Header = () => (
279 | -
280 | +
281 | -
Avatar
282 | +
Avatar
283 | -
Id
284 | +
Id
285 |
Name
286 |
287 | );
288 |
289 | ```
290 |
291 | _./components/users/row.tsx_
292 |
293 | ```diff
294 | import * as Next from 'next';
295 | import Link from 'next/link';
296 | import { User } from '../../model/user';
297 | + const styles = require('./row.css');
298 | ...
299 |
300 | export const Row: Next.NextStatelessComponent = (props) => (
301 | -
322 | ...
323 |
324 | ```
325 |
326 |
--------------------------------------------------------------------------------
/07-styles/readme_es.md:
--------------------------------------------------------------------------------
1 | # Añadiendo estilos con Next,js y CSS
2 |
3 | En este ejemplo, vamos a cubrir los aspectos básicos de la configuración de Next.js para incorporar estilos externamente en ficheros CSS
4 |
5 | # Paso para incluir archivos CSS
6 |
7 | - Tomaremos el ejemplo 06 como punto de partida.
8 |
9 | - Como de costumbre, el primer paso sería instalar las dependencias especificadas en el archivo package.json.
10 |
11 | ```bash
12 | npm install
13 | ```
14 |
15 | - Para poder utilizar archivos CSS en nuestra aplicación, nos hace falta añadir una nueva dependencia al proyecto. Concretamente, se trata del plugin ```next-css```
16 |
17 | ```bash
18 | npm install @zeit/next-css --save
19 | ```
20 |
21 | - Este plugin nos facilita una función ```withCSS``` que podemos invocar desde nuestro archivo de configuración ```next.config.js``` para añadir la funcionalidad necesaria para gestionar archivos CSS en nuestro empaquetado. Para ello, es necesario modificar nuestro fichero ```next.config.js``` tal cual sigue:
22 |
23 | _./next.config.js_
24 |
25 | ```diff
26 | const withTypescript = require('@zeit/next-typescript');
27 | + const withCSS = require('@zeit/next-css');
28 |
29 | - module.exports = withTypescript();
30 | + module.exports = withTypescript(withCSS());
31 |
32 | ```
33 |
34 | - Con esto ya podemos procesar archivos CSS. El siguiente paso sería crear unos estilos en un fichero externo para probarlo. Creamos pues un fichero ```global-styles.css```:
35 |
36 | _./styles/global-styles.css_
37 |
38 | ```
39 | .blue-box {
40 | border: 3px solid blue;
41 | }
42 | ```
43 |
44 | - Y ahora hacemos uso de esta nueva clase en nuestra lista de usuarios. Por ejemplo, podemos aplicar la nueva regla de estilos a la columna 'Avatar' de nuestro componente cabecera.
45 |
46 | _./components/users/header.tsx_
47 |
48 | ```diff
49 | + import '../../styles/global-styles.css';
50 |
51 | export const Header = () => (
52 |
53 | -
Avatar
54 | +
Avatar
55 |
Id
56 |
Name
57 |
58 | )
59 |
60 | ```
61 |
62 | - Si fueramos a lanzar nuestra aplicación ahora, no veríamos, sin embargo, ningún cambio. Esto se debe a que todavía no le hemos dicho a Next.js como deberíamos enlazar este nuevo recurso con nuestra aplicación web. Por defecto, ```next-css``` compila nuestros archivos CSS en ```.next/static/style.css```, y estos contenidos se sirven desde la siguiente url
63 |
64 | ```
65 | /_next/static/style.css
66 | ```
67 |
68 | - Por tanto, necesitamos indicarle a nuestra aplicación que use los estilos desde ese punto de entrada. Para conseguirlo, debemos indicarle a Next.js que utilice un documento personalizado como base, en el cual se indique mediante el uso de elementos ```link``` en la cabecera la fuente de entrada para nuestros estilos CSS. Podemos conseguir esto mediante la creación de un objeto ```_document.js``` (¡ojo, que debe llevar un guión bajo en el nombre!) dentro de nuestra carpeta ```pages```. Dicho archivo contendría el siguiente código:
69 |
70 | _./pages/\_document.js_
71 |
72 | ```javascript
73 | import Document, { Head, Main, NextScript } from 'next/document';
74 |
75 | export default class MyDocument extends Document {
76 | render() {
77 | return (
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | ```
92 |
93 | - Ahora sí que podemos lanzar nuestra app con ```npm run dev```, y comprobaremos que, efectivamente, la parte de la cabecera correspondiente a 'Avatar' queda encuadrada dentro de un rectángulo azúl.
94 |
95 | # Pasos para añadir CSS utilizando el modelo de CSS-Modules
96 |
97 | - Si queremos utilizar módulos CSS en nuestra aplicación, basta con habilitar el correspondiente flag en la configuración base de nuestro archivo ```next.config.js```.
98 |
99 | _./next.config.js_
100 |
101 | ```diff
102 | const withTypescript = require('@zeit/next-typescript');
103 | const withCSS = require('@zeit/next-css');
104 |
105 | - module.exports = withTypescript(withCSS());
106 | + module.exports = withTypescript(
107 | + withCSS({
108 | + cssModules: true,
109 | + cssLoaderOptions: {
110 | + camelCase: true,
111 | + },
112 | + })
113 | + );
114 |
115 | ```
116 |
117 | - Tras hacer esto, podemos crear ahora un archivo ```header.css``` dentro de la carpeta del componente que renderiza la lista de usuarios, y anexarle a este archivo algunas reglas de estilo. Por ejemplo:
118 |
119 | _./components/users/header.css_
120 |
121 | ```css
122 | .purple-box {
123 | border: 2px dotted purple;
124 | }
125 | ```
126 |
127 | - Ahora podemos, por ejemplo, modificar nuestro componente cabecera para aplicar el nuevo estilo al elemento 'Id'. Resaltar que al haber utilizado kebab-case, necesitamos utilizar una propiedad calculada para acceder a la clase CSS en concreto.
128 |
129 | _./components/users/header.tsx_
130 |
131 | ```diff
132 | import '../../styles/global-styles.css';
133 | + const styles = require('./header.css');
134 |
135 | export const Header = () => (
136 |
137 |
Avatar
138 | -
Id
139 | +
Id
140 |
Name
141 |
142 | );
143 |
144 | ```
145 |
146 | - Si lanzamos nuestra aplicación, veremos que en efecto el elemento Id queda envuelto en un cuadro púrpura punteado. Además, el rectángulo azúl del elemento Avatar ha desaparecido. Esto es así debido a que ahora todos los CSS se compilan usando el patrón de módulo CSS. Por tanto, no podemos utilizar clases sólo con el nombre literal de la regla CSS en cuestión. De hecho, podemos comprobar que las dos clases creadas siguen estando en nuestra aplicación compilada. Basta con ir a la consola del navegador y mirar las fuentes para el archivo _next/static/style.css, que debe contener tanto la regla para el rectángulo azúl como el púrpura.
147 |
148 | - Vamos a refactorizar un poco el código para dejar todos los estilos acordes al patrón de Módulos CSS. Borramos la carpeta ```styles``` anteriormente creada y en su lugar añadimos la regla del cuadro azúl a nuestro fichero ```header.css```. Además, le quitamos el guión de enmedio para no tener que utilizar una propiedad computada para referirlo en el componente.
149 |
150 | _./styles/global-styles.css_
151 |
152 | ```diff
153 | - .blue-box {
154 | - border: 3px solid blue;
155 | - }
156 |
157 | ```
158 |
159 | _./components/users/header.css_
160 |
161 | ```diff
162 | .purple-box {
163 | border: 2px dotted purple;
164 | }
165 |
166 | +.blue-box {
167 | + border: 3px solid blue;
168 | +}
169 | ```
170 |
171 | - Por último, refactorizamos el componente cabecera de forma acorde.
172 |
173 | _./components/users/header.tsx_
174 |
175 | ```diff
176 | - import '../../styles/global-styles.css';
177 | const styles = require('./header.css');
178 |
179 | export const Header = () => (
180 |
181 | -
Avatar
182 | +
Avatar
183 |
Id
184 |
Name
185 |
186 | );
187 |
188 | ```
189 |
190 | - Ahora deberíamos de ser capaces de ver los dos estilos aplicados al unísono.
191 |
192 | - Para finalizar, la función ```withCSS``` básicamente nos ofrece azucarillo sintáctico en forma de composición funcional para simplificar la configuración de webpack que subyace. De hecho, internamente utiliza un ```css-loader``` para gestionar los ficheros CSS. Podemos indicar opciones de configuración propias de dicho módulo (ver [webpack's css loader options](https://github.com/webpack-contrib/css-loader#options) para más detalles), como, por ejemplo, añadir ids de ámbito local a nuestras clases CSS. Si modificamos el fichero ```next.config.js``` conforme a lo siguiente:
193 |
194 | _./next.config.js_
195 |
196 | ```diff
197 | const withTypescript = require('@zeit/next-typescript');
198 | const withCSS = require('@zeit/next-css');
199 |
200 | module.exports = withTypescript(
201 | withCSS({
202 | cssModules: true,
203 | cssLoaderOptions: {
204 | camelCase: true,
205 | + importLoaders: 1,
206 | + localIdentName: '[local]___[hash:base64:5]',
207 | },
208 | })
209 | );
210 |
211 | ```
212 |
213 | - Y comprobamos ahora las clases que se muestran en la consola del navegador para la fuente ```_next/static/styles.css```, podemos corroborar que en lugar de la cadena hash ostentosamente más larga que había antes, ahora las clases tienen nombres que se ajustan al patrón indicado en ```cssLoaderOptions.localIdentName```.
214 |
215 | - Como estamos usando `css-modules`, no necesitamos la pagina `_document.js` para cargar los ficheros css. Asi que podemos eliminarla.
216 |
217 | ## Apendice
218 |
219 | - Para dar finalmente por terminado el ejemplo, vamos a refactorizar nuestro código para aplicar un estilado más elegante a nuestra tabla de usuarios:
220 |
221 | _./components/users/header.css_
222 |
223 | ```diff
224 | - .purple-box {
225 | - border: 2px dotted purple;
226 | - }
227 |
228 | - .blue-box {
229 | - border: 3px solid blue;
230 | - }
231 |
232 | + .header {
233 | + background-color: #ddefef;
234 | + border: solid 1px #ddeeee;
235 | + color: #336b6b;
236 | + padding: 10px;
237 | + text-align: left;
238 | + text-shadow: 1px 1px 1px #fff;
239 | + }
240 |
241 | ```
242 |
243 | - Actualizamos `row` y `table`:
244 |
245 | _./components/users/row.css_
246 |
247 | ```css
248 | .row {
249 | border: solid 1px #DDEEEE;
250 | color: #333;
251 | padding: 10px;
252 | text-shadow: 1px 1px 1px #fff;
253 | }
254 |
255 | ```
256 |
257 | _./components/users/table.css_
258 |
259 | ```css
260 | .table {
261 | border: solid 1px #ddeeee;
262 | border-collapse: collapse;
263 | border-spacing: 0;
264 | font: normal 13px Arial, sans-serif;
265 | }
266 |
267 | ```
268 |
269 | - Y por último, modificamos los componentes para importar las nuevas reglas de estilo:
270 |
271 | _./components/users/header.tsx_
272 |
273 | ```diff
274 | const styles = require('./header.css');
275 |
276 | export const Header = () => (
277 | -
278 | +
279 | -
Avatar
280 | +
Avatar
281 | -
Id
282 | +
Id
283 |
Name
284 |
285 | );
286 |
287 | ```
288 |
289 | _./components/users/row.tsx_
290 |
291 | ```diff
292 | import * as Next from 'next';
293 | import Link from 'next/link';
294 | import { User } from '../../model/user';
295 | + const styles = require('./row.css');
296 | ...
297 |
298 | export const Row: Next.NextStatelessComponent = (props) => (
299 | -