├── .gitignore ├── 00-hello-next ├── package.json ├── pages │ └── index.js ├── readme.md └── readme_es.md ├── 01-hello-typescript ├── .babelrc ├── next.config.js ├── package.json ├── pages │ └── index.tsx ├── readme.md ├── readme_es.md └── tsconfig.json ├── 02-navigation ├── .babelrc ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md └── tsconfig.json ├── 03-fetch ├── .babelrc ├── components │ └── users │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ └── user.ts ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts └── tsconfig.json ├── 04-querystring ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ └── user.ts ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts └── tsconfig.json ├── 05-friendly-url ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ └── user.ts ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── 06-detail-page ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ ├── user-detail.ts │ └── user.ts ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── 07-styles ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.css │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ ├── user-detail.ts │ └── user.ts ├── next.config.js ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── 08-serverless ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.css │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ ├── user-detail.ts │ └── user.ts ├── next.config.js ├── now.json ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── 09-css-in-js ├── .babelrc ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.styles.tsx │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ ├── user-detail.ts │ └── user.ts ├── next.config.js ├── now.json ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── readme_es.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── 10-production-mode ├── .babelrc ├── .env ├── .vscode │ └── launch.json ├── components │ └── users │ │ ├── header.styles.tsx │ │ ├── header.tsx │ │ ├── index.ts │ │ ├── row.tsx │ │ └── table.tsx ├── model │ ├── user-detail.ts │ └── user.ts ├── next.config.js ├── now.json ├── package.json ├── pages │ ├── index.tsx │ └── user-info.tsx ├── readme.md ├── rest-api │ └── github.ts ├── server.js └── tsconfig.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bundles 3 | .next 4 | .DS_Store 5 | dist/ 6 | typings/ 7 | *.orig 8 | .idea/ 9 | */src/**/*.js.map 10 | yarn.lock 11 | *.log 12 | lib/ 13 | es/ 14 | package-lock.json 15 | -------------------------------------------------------------------------------- /00-hello-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^8.0.4", 14 | "react": "^16.8.6", 15 | "react-dom": "^16.8.6" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /00-hello-next/pages/index.js: -------------------------------------------------------------------------------- 1 | const Index = () => ( 2 |
3 |

Hello Next.js

4 |
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 |
42 |

Hello Next.js

43 |
44 | ); 45 | 46 | export default Index; 47 | ``` 48 | 49 | - Ejecutamos el ejemplo: 50 | 51 | ```bash 52 | npm run dev 53 | ``` 54 | 55 | - Ahora podemos abrir un navegador y apuntar a: http://localhost:3000 y ver los resultados. 56 | -------------------------------------------------------------------------------- /01-hello-typescript/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /01-hello-typescript/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /01-hello-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "next": "^8.0.4", 15 | "react": "^16.8.6", 16 | "react-dom": "^16.8.6" 17 | }, 18 | "devDependencies": { 19 | "@types/next": "^8.0.3", 20 | "@types/react": "^16.8.13", 21 | "typescript": "^3.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /01-hello-typescript/pages/index.tsx: -------------------------------------------------------------------------------- 1 | const myLanguage: string = "Typescript"; 2 | 3 | const Index = () => ( 4 |
5 |

Hello Next.js

6 |

From {myLanguage}

7 |
8 | ); 9 | 10 | export default Index; 11 | -------------------------------------------------------------------------------- /01-hello-typescript/readme.md: -------------------------------------------------------------------------------- 1 | # Hello typescript 2 | 3 | In this example we will add Typescript support to our project, we will start from sample _00-hello-next_. 4 | 5 | At the moment of the writing of this sample Babel 7 was on beta (Babel 7 will have typescript support), we will use 6 | a plugin based on Babel 7: https://github.com/zeit/next-plugins/tree/master/packages/next-typescript 7 | 8 | This is the official typescript plugin for next. 9 | 10 | # Steps 11 | 12 | - To get started copy the files from sample _00-hello-next_ and execute: 13 | 14 | ```bash 15 | npm install 16 | ``` 17 | 18 | - Let's install Typescript plus _next-typescript_ plugin. 19 | 20 | ``` 21 | npm install @zeit/next-typescript --save 22 | ``` 23 | 24 | - And the typings. 25 | 26 | ``` 27 | npm install typescript @types/next @types/react --save-dev 28 | ``` 29 | 30 | - We need to add a typescript configuration. 31 | 32 | _./tsconfig.json_ 33 | 34 | ```json 35 | { 36 | "compilerOptions": { 37 | "target": "esnext", 38 | "module": "esnext", 39 | "jsx": "preserve", 40 | "allowJs": true, 41 | "moduleResolution": "node", 42 | "allowSyntheticDefaultImports": true, 43 | "noUnusedLocals": true, 44 | "noUnusedParameters": true, 45 | "removeComments": false, 46 | "preserveConstEnums": true, 47 | "sourceMap": true, 48 | "skipLibCheck": true, 49 | "baseUrl": ".", 50 | "lib": [ 51 | "dom", 52 | "es2016" 53 | ] 54 | } 55 | } 56 | ``` 57 | 58 | - Next allow us to extend it's default webpack configuration, 59 | let's create a _next.config.js_ file and include the Typescript configuration. 60 | 61 | _./next.config.js_ 62 | 63 | ```javascript 64 | const withTypescript = require('@zeit/next-typescript'); 65 | 66 | module.exports = withTypescript(); 67 | 68 | ``` 69 | 70 | - Babel needs to be configured to work. We create ./.babelrc in the root folder. In this example, we use this .babelrc: 71 | 72 | _[./.babelrc](./.babelrc)_ 73 | 74 | ```json 75 | { 76 | "presets": [ 77 | "next/babel", 78 | "@zeit/next-typescript/babel" 79 | ] 80 | } 81 | 82 | ``` 83 | 84 | - Let's rename our _index.js_ to _index.tsx_. 85 | 86 | - Let's add some simple typescript code to check that 87 | transpilation is working as expected. 88 | 89 | ```diff 90 | + const myLanguage : string = "Typescript"; 91 | 92 | const Index = () => ( 93 |
94 |

Hello Next.js

95 | +

From {myLanguage}

96 |
97 | ); 98 | 99 | export default Index; 100 | ``` 101 | 102 | - Let's try the sample 103 | 104 | ```bash 105 | npm run dev 106 | ``` 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /01-hello-typescript/readme_es.md: -------------------------------------------------------------------------------- 1 | # Hello typescript 2 | 3 | En este ejemplo vamos a añadir soporte Typescript a nuestro proyecto, partiremos del ejemplo _00-hello-next_. 4 | 5 | En el momento de escribir este ejemplo Babel 7 estaba en versión beta (Babel 7 tendrá soporte para typescript), usaremos un plugin basado en Babel 7: https://github.com/zeit/next-plugins/tree/master/packages/next-typescript 6 | 7 | Este es el plugin oficial de typescript para next. 8 | 9 | # Pasos 10 | 11 | - Para comenzar copia los fichero del ejemplo _00-hello-next_ y ejecuta: 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | - Instalemos el plugin _next-typescript_. 18 | 19 | ``` 20 | npm install @zeit/next-typescript --save 21 | ``` 22 | 23 | - Y los typings. 24 | 25 | ``` 26 | npm install typescript @types/next @types/react --save-dev 27 | ``` 28 | 29 | - Necesitamos añadir una configuración typescript. 30 | 31 | _./tsconfig.json_ 32 | 33 | ```json 34 | { 35 | "compilerOptions": { 36 | "target": "esnext", 37 | "module": "esnext", 38 | "jsx": "preserve", 39 | "allowJs": true, 40 | "moduleResolution": "node", 41 | "allowSyntheticDefaultImports": true, 42 | "noUnusedLocals": true, 43 | "noUnusedParameters": true, 44 | "removeComments": false, 45 | "preserveConstEnums": true, 46 | "sourceMap": true, 47 | "skipLibCheck": true, 48 | "baseUrl": ".", 49 | "lib": [ 50 | "dom", 51 | "es2016" 52 | ] 53 | } 54 | } 55 | ``` 56 | 57 | - Next nos permite extender la configuración por defecto de webpack, 58 | creemos un fichero _next.config.js_ file e incluyamos la configuración Typescript. 59 | 60 | _./next.config.js_ 61 | 62 | ```javascript 63 | const withTypescript = require('@zeit/next-typescript'); 64 | 65 | module.exports = withTypescript(); 66 | 67 | ``` 68 | 69 | - Necesitamos configurar Babel para que funcione. Creamos un ./.babelrc en la carpeta raíz. En este ejemplo vamos a utilizar el siguien .babelrc: 70 | 71 | _[./.babelrc](./.babelrc)_ 72 | 73 | ```json 74 | { 75 | "presets": [ 76 | "next/babel", 77 | "@zeit/next-typescript/babel" 78 | ] 79 | } 80 | 81 | ``` 82 | 83 | - Renombremos nuestro _index.js_ a _index.tsx_. 84 | 85 | - Añadamos algo de codigo typescript simple para comprobar que la transpilación funciona como esperamos. 86 | 87 | ```diff 88 | + const myLanguage = "Typescript"; 89 | 90 | const Index = () => ( 91 |
92 |

Hello Next.js

93 | +

From {myLanguage}

94 |
95 | ); 96 | 97 | export default Index; 98 | ``` 99 | 100 | - Probemos el ejemplo 101 | 102 | ```bash 103 | npm run dev 104 | ``` 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /01-hello-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /02-navigation/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /02-navigation/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /02-navigation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "next": "^8.0.4", 15 | "react": "^16.8.6", 16 | "react-dom": "^16.8.6" 17 | }, 18 | "devDependencies": { 19 | "@types/next": "^8.0.3", 20 | "@types/react": "^16.8.13", 21 | "typescript": "^3.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /02-navigation/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | const myLanguage: string = "Typescript"; 4 | 5 | const Index = () => ( 6 |
7 |

Hello Next.js

8 |

From {myLanguage}

9 | 10 | Navigate to user info page 11 | 12 |
13 | ); 14 | 15 | export default Index; 16 | -------------------------------------------------------------------------------- /02-navigation/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | const UserInfoPage = () => ( 2 |
3 |

I'm the user infopage

4 |
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 |
40 |

Hello Next.js

41 |

From {myLanguage}

42 | + 43 | + Navigate to user info page 44 | + 45 |
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 |
39 |

Hello Next.js

40 |

From {myLanguage}

41 | + 42 | + Navigate to user info page 43 | + 44 |
45 | ); 46 | 47 | export default Index; 48 | ``` 49 | 50 | > Si no usuamos este objeto _Link_ cuando clicamos en el link, este navegará en el lado del servidor en lugar del lado del cliente. 51 | 52 | - Ahora, ejecuta el ejemplo 53 | 54 | ```bash 55 | npm run dev 56 | ``` 57 | 58 | > Ahora puedes observar que hay navegación entre cliente y servidor. Además el histórico del navegador se rellena con dicha navegación. 59 | > Ver diferencias si usamos el elemento `a`. 60 | -------------------------------------------------------------------------------- /02-navigation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /03-fetch/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /03-fetch/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | export const Header = () => ( 2 | 3 | Avatar 4 | Id 5 | Name 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /03-fetch/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /03-fetch/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | 4 | interface Props { 5 | user: User; 6 | } 7 | 8 | export const Row: Next.NextStatelessComponent = (props) => ( 9 | 10 | 11 | 12 | 13 | 14 | {props.user.id} 15 | 16 | 17 | {props.user.login} 18 | 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /03-fetch/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 | 12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /03-fetch/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /03-fetch/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /03-fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "isomorphic-unfetch": "^3.0.0", 15 | "next": "^8.0.4", 16 | "react": "^16.8.6", 17 | "react-dom": "^16.8.6" 18 | }, 19 | "devDependencies": { 20 | "@types/next": "^8.0.3", 21 | "@types/react": "^16.8.13", 22 | "typescript": "^3.4.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /03-fetch/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /03-fetch/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | const UserInfoPage = () => ( 2 |
3 |

I'm the user infopage

4 |
5 | ) 6 | 7 | export default UserInfoPage; 8 | -------------------------------------------------------------------------------- /03-fetch/readme.md: -------------------------------------------------------------------------------- 1 | # Fetching data 2 | 3 | It's time to fetch data from a remote source, let consume a Github api end point to retrieve a list 4 | of members that belong to a group. 5 | 6 | # Steps 7 | 8 | - We will take as starting point _02-navigation_ (let's copy that sample into a new subfolder). 9 | 10 | - Let's install the dependencies. 11 | 12 | ```bash 13 | npm install 14 | ``` 15 | 16 | - In order to perform a fetch call on both server and client side we will install the package 17 | _isomorphic-unfetch_. 18 | 19 | ```bash 20 | npm install isomorphic-unfetch --save 21 | ``` 22 | 23 | - let's create a model. 24 | 25 | _./model/user.ts_ 26 | 27 | ```typescript 28 | export interface User { 29 | login: string; 30 | id: number; 31 | avatar_url: string; 32 | } 33 | 34 | ``` 35 | 36 | - Let's create a simple _rest-api_ entry: 37 | 38 | _./rest-api/github.ts_ 39 | 40 | ```typescript 41 | import { User } from '../model/user'; 42 | import fetch from 'isomorphic-unfetch'; 43 | 44 | const baseRoot = 'https://api.github.com/orgs/lemoncode'; 45 | const userCollectionURL = `${baseRoot}/members` 46 | 47 | export const fetchUsers = async (): Promise => { 48 | const res = await fetch(userCollectionURL) 49 | const data = await res.json(); 50 | 51 | return data.map( 52 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 53 | ); 54 | } 55 | 56 | ``` 57 | 58 | - Let's consume it in our _index_ page, first we will just console out the api call result. 59 | One important thing to note down, we will make use of _getInitialProps_ this new hook 60 | allows us to make a call from the server side or client side. 61 | 62 | _./pages/index.tsx_ 63 | 64 | ```diff 65 | + import * as Next from 'next'; 66 | import Link from 'next/link'; 67 | + import { fetchUsers } from '../rest-api/github'; 68 | + import { User } from '../model/user'; 69 | 70 | - const myLanguage = "Typescript"; 71 | 72 | + interface Props { 73 | + users: User[]; 74 | + } 75 | 76 | - const Index = () => ( 77 | + const Index: Next.NextStatelessComponent = (props) => ( 78 |
79 |

Hello Next.js

80 | -

From {myLanguage}

81 | 82 | Navigate to user info page 83 | 84 |
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 |
110 | 111 | 112 | 113 | 114 | ) 115 | 116 | ``` 117 | 118 | _./components/users/row.tsx_ 119 | 120 | ```typescript 121 | import * as Next from 'next'; 122 | import { User } from '../../model/user'; 123 | 124 | interface Props { 125 | user: User; 126 | } 127 | 128 | export const Row: Next.NextStatelessComponent = (props) => ( 129 | 130 | 133 | 136 | 139 | 140 | ) 141 | 142 | ``` 143 | 144 | _./components/users/table.tsx_ 145 | 146 | ```typescript 147 | import * as Next from 'next'; 148 | import { User } from '../../model/user'; 149 | import { Header } from './header'; 150 | import { Row } from './row'; 151 | 152 | interface Props { 153 | users: User[]; 154 | } 155 | 156 | export const Table: Next.NextStatelessComponent = (props) => ( 157 |
AvatarIdName
131 | 132 | 134 | {props.user.id} 135 | 137 | {props.user.login} 138 |
158 | 159 |
160 |
161 | 162 | { 163 | props.users.map(user => ( 164 | 165 | )) 166 | } 167 | 168 |
169 | ) 170 | 171 | ``` 172 | 173 | _./components/users/index.ts_ 174 | 175 | ```typescript 176 | export * from './table'; 177 | 178 | ``` 179 | 180 | - Let's update the page. 181 | 182 | _./pages/index.tsx_ 183 | 184 | ```diff 185 | ... 186 | import { User } from '../model/user'; 187 | + import { Table } from '../components/users'; 188 | 189 | ... 190 | 191 | const Index: Next.NextStatelessComponent = (props) => ( 192 |
193 |

Hello Next.js

194 | + 195 | 196 | Navigate to user info page 197 | 198 | 199 | ); 200 | ... 201 | 202 | ``` 203 | 204 | ## Appendix 205 | 206 | What if we want to store the list of users in the component State instead of Properties, we could do it in this way: 207 | 208 | _./pages/index.tsx_ 209 | 210 | ```diff 211 | + import * as React from 'react'; 212 | import * as Next from 'next'; 213 | import Link from 'next/link'; 214 | import { fetchUsers } from '../rest-api/github'; 215 | import { User } from '../model/user'; 216 | import { Table } from '../components/users'; 217 | 218 | interface Props { 219 | users: User[]; 220 | } 221 | 222 | - const Index: Next.NextStatelessComponent = (props) => ( 223 | + const Index: Next.NextStatelessComponent = (props) => { 224 | + const [users] = React.useState(props.users); 225 | + console.log('Rendering users'); 226 | 227 | + return ( 228 |
229 |

Hello Next.js

230 | -
231 | +
232 | 233 | Navigate to user info page 234 | 235 | 236 | ); 237 | + } 238 | 239 | Index.getInitialProps = async () => { 240 | const users = await fetchUsers(); 241 | - console.log(users); 242 | + console.log('Initial Props'); 243 | 244 | return { 245 | users, 246 | } 247 | } 248 | 249 | export default Index; 250 | 251 | ``` 252 | -------------------------------------------------------------------------------- /03-fetch/readme_es.md: -------------------------------------------------------------------------------- 1 | # Recuperando datos 2 | 3 | Ha llegado el momento de recuperar datos desde una fuente remota, vamos a consumir datos a través de un servicio de github para recuperar una lista de miembros que pertenecen a un grupo. 4 | 5 | # Pasos 6 | - El punto de comienzo de este ejemplo es _02-navigation_ (vamos a copiar este ejemplo en una nueva subcarpeta) 7 | 8 | - A continuación instalaremos las dependencias 9 | 10 | ```bash 11 | npm install 12 | ``` 13 | 14 | - Para realizar la recuperación de datos en el lado de servidor y en el de cliente vamos a instalar el paquete _isomorphic-unfetch_. 15 | 16 | ```bash 17 | npm install isomorphic-unfetch --save 18 | ``` 19 | 20 | 21 | - A continuación vamos a crear un modelo. 22 | 23 | _./model/user.ts_ 24 | 25 | ```typescript 26 | export interface User { 27 | login: string; 28 | id: number; 29 | avatar_url: string; 30 | } 31 | 32 | ``` 33 | 34 | - Vamos a crear una rest-api simple: 35 | 36 | _./rest-api/github.ts_ 37 | 38 | ```typescript 39 | import { User } from '../model/user'; 40 | import fetch from 'isomorphic-unfetch'; 41 | 42 | const baseRoot = 'https://api.github.com/orgs/lemoncode'; 43 | const userCollectionURL = `${baseRoot}/members` 44 | 45 | export const fetchUsers = async (): Promise => { 46 | const res = await fetch(userCollectionURL) 47 | const data = await res.json(); 48 | 49 | return data.map( 50 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 51 | ); 52 | } 53 | 54 | ``` 55 | 56 | - Vamos a consumir los datos en nuestra página principal, primero simplemente mostraremos por consola el resultado de la llamada a la api. Una cosa importante a remarcar, vamos a hacer uso de getInitialProps que nos permite realizar una llamada desde el lado del servidor o del cliente. 57 | 58 | _./pages/index.tsx_ 59 | 60 | ```diff 61 | + import * as Next from 'next'; 62 | import Link from 'next/link'; 63 | + import { fetchUsers } from '../rest-api/github'; 64 | + import { User } from '../model/user'; 65 | 66 | - const myLanguage = "Typescript"; 67 | 68 | + interface Props { 69 | + users: User[]; 70 | + } 71 | 72 | - const Index = () => ( 73 | + const Index: Next.NextStatelessComponent = (props) => ( 74 |
75 |

Hello Next.js

76 | -

From {myLanguage}

77 | 78 | Navigate to user info page 79 | 80 |
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 |
106 | 107 | 108 | 109 | 110 | ) 111 | 112 | ``` 113 | 114 | _./components/users/row.tsx_ 115 | 116 | ```typescript 117 | import * as Next from 'next'; 118 | import { User } from '../../model/user'; 119 | 120 | interface Props { 121 | user: User; 122 | } 123 | 124 | export const Row: Next.NextStatelessComponent = (props) => ( 125 | 126 | 129 | 132 | 135 | 136 | ) 137 | 138 | ``` 139 | 140 | _./components/users/table.tsx_ 141 | 142 | ```typescript 143 | import * as Next from 'next'; 144 | import { User } from '../../model/user'; 145 | import { Header } from './header'; 146 | import { Row } from './row'; 147 | 148 | interface Props { 149 | users: User[]; 150 | } 151 | 152 | export const Table: Next.NextStatelessComponent = (props) => ( 153 |
AvatarIdName
127 | 128 | 130 | {props.user.id} 131 | 133 | {props.user.login} 134 |
154 | 155 |
156 |
157 | 158 | { 159 | props.users.map(user => ( 160 | 161 | )) 162 | } 163 | 164 |
165 | ) 166 | 167 | ``` 168 | 169 | _./components/users/index.ts_ 170 | 171 | ```typescript 172 | export * from './table'; 173 | 174 | ``` 175 | 176 | - Actualicemos la página. 177 | 178 | _./pages/index.tsx_ 179 | 180 | ```diff 181 | ... 182 | import { User } from '../model/user'; 183 | + import { Table } from '../components/users'; 184 | 185 | ... 186 | 187 | const Index: Next.NextStatelessComponent = (props) => ( 188 |
189 |

Hello Next.js

190 | + 191 | 192 | Navigate to user info page 193 | 194 | 195 | ); 196 | ... 197 | 198 | ``` 199 | 200 | ## Apendice 201 | 202 | Si preferimos mantener la lista de usuarios en el estado del componente en lugar de en las propiedades, podemos hacerlo de la siguiente manera: 203 | 204 | _./pages/index.tsx_ 205 | 206 | ```diff 207 | + import * as React from 'react'; 208 | import * as Next from 'next'; 209 | import Link from 'next/link'; 210 | import { fetchUsers } from '../rest-api/github'; 211 | import { User } from '../model/user'; 212 | import { Table } from '../components/users'; 213 | 214 | interface Props { 215 | users: User[]; 216 | } 217 | 218 | - const Index: Next.NextStatelessComponent = (props) => ( 219 | + const Index: Next.NextStatelessComponent = (props) => { 220 | + const [users] = React.useState(props.users); 221 | + console.log('Rendering users'); 222 | 223 | + return ( 224 |
225 |

Hello Next.js

226 | -
227 | +
228 | 229 | Navigate to user info page 230 | 231 | 232 | ); 233 | + } 234 | 235 | Index.getInitialProps = async () => { 236 | const users = await fetchUsers(); 237 | - console.log(users); 238 | + console.log('Initial Props'); 239 | 240 | return { 241 | users, 242 | } 243 | } 244 | 245 | export default Index; 246 | 247 | ``` 248 | -------------------------------------------------------------------------------- /03-fetch/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import fetch from 'isomorphic-unfetch'; 3 | 4 | const baseRoot = 'https://api.github.com/orgs/lemoncode'; 5 | const userCollectionURL = `${baseRoot}/members` 6 | 7 | export const fetchUsers = async (): Promise => { 8 | const res = await fetch(userCollectionURL) 9 | const data = await res.json(); 10 | 11 | return data.map( 12 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /03-fetch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /04-querystring/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /04-querystring/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /04-querystring/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | export const Header = () => ( 2 | 3 | 4 | 5 | 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /04-querystring/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /04-querystring/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /04-querystring/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
AvatarIdName
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /04-querystring/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /04-querystring/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /04-querystring/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "isomorphic-unfetch": "^3.0.0", 15 | "next": "^8.0.4", 16 | "react": "^16.8.6", 17 | "react-dom": "^16.8.6" 18 | }, 19 | "devDependencies": { 20 | "@types/next": "^8.0.3", 21 | "@types/react": "^16.8.13", 22 | "typescript": "^3.4.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /04-querystring/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /04-querystring/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import { withRouter } from 'next/router'; 2 | 3 | const UserInfoPage = withRouter((props) => ( 4 |
5 |

I'm the user infopage

6 |

{props.router.query.login}

7 |
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 | 41 | ... 42 | 43 | ``` 44 | 45 | - Now let's read the query string param from the _user-info_ page, in order to do that we will 46 | make use of _nextjs_ _withRouter_ HOC. 47 | 48 | _./pages/user-info.tsx_ 49 | 50 | ```diff 51 | + import { withRouter } from 'next/router'; 52 | 53 | - const UserInfoPage = () => ( 54 | + const UserInfoPage = withRouter((props) => ( 55 |
56 |

I'm the user info page

57 | +

{props.router.query.login}

58 |
59 | -); 60 | +)); 61 | 62 | export default UserInfoPage; 63 | ``` 64 | 65 | - Let's run the sample. 66 | 67 | ```bash 68 | npm run dev 69 | ``` 70 | 71 | - Add debugging: 72 | 73 | ### ./.vscode/launch.json 74 | 75 | ```json 76 | { 77 | "version": "0.2.0", 78 | "configurations": [ 79 | { 80 | "type": "node", 81 | "request": "launch", 82 | "name": "Debug Next", 83 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 84 | "port": 9229, 85 | "env": { 86 | "NODE_OPTIONS": "--inspect" 87 | } 88 | } 89 | ] 90 | } 91 | 92 | ``` 93 | 94 | - If you want to play a little bit more with query string just add: 95 | 96 | _./pages/components/users/row.tsx_ 97 | 98 | ```diff 99 | 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 | 42 | ... 43 | 44 | ``` 45 | 46 | - Ahora vamos a leer el paramétro query string desde la página de información del usuario _user-info_, para lograrlo haremos uso del _nextjs_ _withRouter_ HOC. 47 | 48 | _./pages/user-info.tsx_ 49 | 50 | ```diff 51 | + import { withRouter } from 'next/router'; 52 | 53 | - const UserInfoPage = () => ( 54 | + const UserInfoPage = withRouter((props) => ( 55 |
56 |

I'm the user info page

57 | +

{props.router.query.login}

58 |
59 | -); 60 | +)); 61 | 62 | export default UserInfoPage; 63 | ``` 64 | 65 | - Ahora ejecutamos el ejemplo. 66 | 67 | ```bash 68 | npm run dev 69 | ``` 70 | 71 | - Si quieres jugar un poco más con las query strings añade: 72 | 73 | _./pages/components/users/row.tsx_ 74 | 75 | ```diff 76 | 82 | ``` 83 | 84 | - Después para ver otro parámetro capturado desde la query string añade esto: 85 | 86 | _./pages/user-info.tsx_ 87 | 88 | ```diff 89 | import { withRouter } from 'next/router'; 90 | 91 | const UserInfoPage = withRouter((props) => ( 92 |
93 |

I'm the user infopage

94 | +

{props.router.query.id}

95 |

{props.router.query.login}

96 |
97 | )); 98 | 99 | export default UserInfoPage; 100 | ``` 101 | 102 | - Ejecuta el ejemplo de nuevo si lo habías parado 103 | 104 | ```bash 105 | npm run dev 106 | ``` 107 | -------------------------------------------------------------------------------- /04-querystring/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import fetch from 'isomorphic-unfetch'; 3 | 4 | const baseRoot = 'https://api.github.com/orgs/lemoncode'; 5 | const userCollectionURL = `${baseRoot}/members` 6 | 7 | export const fetchUsers = async (): Promise => { 8 | const res = await fetch(userCollectionURL) 9 | const data = await res.json(); 10 | 11 | return data.map( 12 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /04-querystring/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /05-friendly-url/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /05-friendly-url/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /05-friendly-url/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | export const Header = () => ( 2 | 3 | 4 | 5 | 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /05-friendly-url/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /05-friendly-url/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /05-friendly-url/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
36 | + 37 | - {props.user.login} 38 | + {props.user.login} 39 | + 40 | 100 | - 101 | + 102 | {props.user.login} 103 | 104 | 37 | + 38 | - {props.user.login} 39 | + {props.user.login} 40 | + 41 | 77 | - 78 | + 79 | {props.user.login} 80 | 81 |
AvatarIdName
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /05-friendly-url/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /05-friendly-url/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /05-friendly-url/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "express": "^4.16.4", 15 | "isomorphic-unfetch": "^3.0.0", 16 | "next": "^8.0.4", 17 | "react": "^16.8.6", 18 | "react-dom": "^16.8.6" 19 | }, 20 | "devDependencies": { 21 | "@types/next": "^8.0.3", 22 | "@types/react": "^16.8.13", 23 | "typescript": "^3.4.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /05-friendly-url/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /05-friendly-url/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import { withRouter } from 'next/router'; 2 | 3 | const UserInfoPage = withRouter((props) => ( 4 |
5 |

I'm the user infopage

6 |

{props.router.query.login}

7 |
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 | 35 | ... 36 | 37 | ``` 38 | 39 | - Ahora comprobamos como funciona ejecutando el ejemplo: 40 | 41 | ```bash 42 | npm run dev 43 | ``` 44 | 45 | - Ya estamos listos? La respuesta es NO. Si refrescáramos la página el server side rendering no estaría funcionando correctamente, por lo que obtendríamos un 404. 46 | 47 | - Para que el servidor se comporte de la misma manera que el cliente, vamos a necesitar añadir más contenido: 48 | 49 | - Instalamos _express_ 50 | 51 | ```bash 52 | npm install express --save 53 | ``` 54 | 55 | - Crearemos un archivo llamado _server.js_ 56 | 57 | _./server.js_ 58 | 59 | ```javascript 60 | const express = require('express'); 61 | const next = require('next'); 62 | 63 | const dev = process.env.NODE_ENV !== 'production'; 64 | const app = next({ dev }); 65 | const handler = app.getRequestHandler(); 66 | 67 | app 68 | .prepare() 69 | .then(() => { 70 | const server = express(); 71 | 72 | server.get('*', (req, res) => { 73 | return handler(req, res); 74 | }); 75 | 76 | server.listen(3000, err => { 77 | if (err) throw err; 78 | console.log('> Ready on http://localhost:3000'); 79 | }); 80 | }) 81 | .catch(ex => { 82 | console.error(ex.stack); 83 | process.exit(1); 84 | }); 85 | 86 | ``` 87 | 88 | > en este archivo solo hemos creado una aplicación "next" escuchando cualquier petición. Esta petición solo será controlada por la aplicación "next". 89 | 90 | - Ahora actualizamos nuestra entrada _package.json_ . 91 | 92 | _./package.json_ 93 | 94 | ```diff 95 | "scripts": { 96 | - "dev": "next" 97 | + "dev": "node server.js" 98 | }, 99 | ``` 100 | 101 | - Vamos a comprobar si el servidor esta funcionando (todavía sin URL limpia del lado del servidor) 102 | 103 | ```bash 104 | npm run dev 105 | ``` 106 | 107 | - Añadiremos un "server.get" para la nueva URL amigable que hemos creado. 108 | 109 | _./server.js_ 110 | 111 | ```diff 112 | ... 113 | 114 | app 115 | .prepare() 116 | .then(() => { 117 | const server = express(); 118 | 119 | + server.get('/user-info/login/:login', (req, res) => { 120 | + return app.render(req, res, '/user-info', { login: req.params.login }); 121 | + }); 122 | 123 | server.get('*', (req, res) => { 124 | return handler(req, res); 125 | }); 126 | ... 127 | 128 | ``` 129 | 130 | - Ahora si ejecutamos el código vamos a ver que la URL amigable funciona de manera correcta, una vez que refresquemos la página. 131 | 132 | ```bash 133 | npm run dev 134 | ``` 135 | -------------------------------------------------------------------------------- /05-friendly-url/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import fetch from 'isomorphic-unfetch'; 3 | 4 | const baseRoot = 'https://api.github.com/orgs/lemoncode'; 5 | const userCollectionURL = `${baseRoot}/members` 6 | 7 | export const fetchUsers = async (): Promise => { 8 | const res = await fetch(userCollectionURL) 9 | const data = await res.json(); 10 | 11 | return data.map( 12 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /05-friendly-url/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | 4 | const dev = process.env.NODE_ENV !== 'production'; 5 | const app = next({ dev }); 6 | const handler = app.getRequestHandler(); 7 | 8 | app 9 | .prepare() 10 | .then(() => { 11 | const server = express(); 12 | 13 | server.get('/user-info/login/:login', (req, res) => { 14 | return app.render(req, res, '/user-info', { login: req.params.login }); 15 | }); 16 | 17 | server.get('*', (req, res) => { 18 | return handler(req, res); 19 | }); 20 | 21 | server.listen(3000, err => { 22 | if (err) throw err; 23 | console.log('> Ready on http://localhost:3000'); 24 | }); 25 | }) 26 | .catch(ex => { 27 | console.error(ex.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /05-friendly-url/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /06-detail-page/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /06-detail-page/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /06-detail-page/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | export const Header = () => ( 2 | 3 | 4 | 5 | 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /06-detail-page/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /06-detail-page/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /06-detail-page/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
32 | - 33 | + 34 | {props.user.login} 35 | 36 | 30 | - 31 | + 32 | {props.user.login} 33 | 34 |
AvatarIdName
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /06-detail-page/model/user-detail.ts: -------------------------------------------------------------------------------- 1 | export interface UserDetail { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | name: string; 6 | company: string; 7 | followers: string; 8 | } 9 | -------------------------------------------------------------------------------- /06-detail-page/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /06-detail-page/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | 3 | module.exports = withTypescript(); 4 | -------------------------------------------------------------------------------- /06-detail-page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-typescript": "^1.1.1", 14 | "express": "^4.16.4", 15 | "isomorphic-unfetch": "^3.0.0", 16 | "next": "^8.0.4", 17 | "react": "^16.8.6", 18 | "react-dom": "^16.8.6" 19 | }, 20 | "devDependencies": { 21 | "@types/next": "^8.0.3", 22 | "@types/react": "^16.8.13", 23 | "typescript": "^3.4.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /06-detail-page/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /06-detail-page/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { fetchUserDetail } from '../rest-api/github'; 3 | import { UserDetail } from '../model/user-detail'; 4 | 5 | interface Props { 6 | login: string; 7 | userDetail: UserDetail; 8 | } 9 | 10 | const UserInfoPage: Next.NextStatelessComponent = props => ( 11 |
12 |

I'm the user infopage

13 |

User ID: {props.userDetail.id}

14 | 15 |

User name: {props.login}

16 |

Company: {props.userDetail.company}

17 |

Followers: {props.userDetail.followers}

18 |
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 | -------------------------------------------------------------------------------- /06-detail-page/readme.md: -------------------------------------------------------------------------------- 1 | # Detail page 2 | 3 | Let's complete the sample by implementing the details page. 4 | 5 | This example won't introduce any new concept, just grab the right id from the query string, 6 | fetch the user detailed data and display it. 7 | 8 | # Steps 9 | 10 | - We will take as starting point sample _05-friendly-url_. 11 | 12 | - Let's install the dependencies. 13 | 14 | ```bash 15 | npm install 16 | ``` 17 | 18 | - Time to create an entity that will hold the user details. 19 | 20 | _./model/user-detail.ts_ 21 | 22 | ```typescript 23 | export interface UserDetail { 24 | login: string; 25 | id: number; 26 | avatar_url: string; 27 | name: string; 28 | company: string; 29 | followers: string; 30 | } 31 | 32 | ``` 33 | 34 | - Let's create a new entry on the api to read the details of the selected user from the github api. 35 | 36 | _./rest-api/github.ts_ 37 | 38 | ```diff 39 | import { User } from '../model/user'; 40 | + import { UserDetail } from '../model/user-detail'; 41 | import fetch from 'isomorphic-unfetch'; 42 | 43 | - const baseRoot = 'https://api.github.com/orgs/lemoncode'; 44 | + const baseRoot = 'https://api.github.com'; 45 | - const userCollectionURL = `${baseRoot}/members` 46 | + const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 47 | + const userDetailURL = `${baseRoot}/users`; 48 | 49 | export const fetchUsers = async () => { 50 | const res = await fetch(userCollectionURL) 51 | const data = await res.json(); 52 | 53 | return data.map( 54 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 55 | ); 56 | } 57 | 58 | + export const fetchUserDetail = async (user: string): Promise => { 59 | + const res = await fetch(`${userDetailURL}/${user}`); 60 | + const { id, login, avatar_url, name, company, followers } = await res.json(); 61 | 62 | + return { 63 | + id, 64 | + login, 65 | + avatar_url, 66 | + name, 67 | + company, 68 | + followers, 69 | + }; 70 | + }; 71 | 72 | ``` 73 | 74 | - Now that we have the data loaded is time to display it on the component, we will just implement something very simple. 75 | 76 | _./pages/user-info.tsx_ 77 | 78 | ```diff 79 | - import { withRouter } from 'next/router'; 80 | + import * as Next from 'next'; 81 | + import { fetchUserDetail } from '../rest-api/github'; 82 | + import { UserDetail } from '../model/user-detail'; 83 | 84 | - const UserInfoPage = withRouter((props) => ( 85 | -
86 | -

I'm the user infopage

87 | -

{props.router.query.login}

88 | -
89 | - )); 90 | 91 | + interface Props { 92 | + login: string; 93 | + userDetail: UserDetail; 94 | + } 95 | 96 | + const UserInfoPage: Next.NextStatelessComponent = props => ( 97 | +
98 | +

I'm the user infopage

99 | +

User ID: {props.userDetail.id}

100 | + 101 | +

User name: {props.login}

102 | +

Company: {props.userDetail.company}

103 | +

Followers: {props.userDetail.followers}

104 | +
105 | + ); 106 | 107 | + UserInfoPage.getInitialProps = async props => { 108 | + const login = props.query.login as string; 109 | + const userDetail = await fetchUserDetail(login); 110 | 111 | + return { 112 | + login, 113 | + userDetail, 114 | + }; 115 | + }; 116 | 117 | export default UserInfoPage; 118 | 119 | ``` 120 | 121 | - Let's give a try: 122 | 123 | ```bash 124 | npm run dev 125 | ``` 126 | -------------------------------------------------------------------------------- /06-detail-page/readme_es.md: -------------------------------------------------------------------------------- 1 | # Página detalle 2 | 3 | Vamos a completar el ejemplo implementando la página de detalles. 4 | 5 | Este ejemplo no introducirá ningún concepto nuevo, sólo toma el id correcto de la query string, busca los datos detallados del usuario y los muestra. 6 | 7 | # Pasos 8 | 9 | - Tomaremos como punto de partida el ejemplo _05-friendly-url_. 10 | 11 | - Instalamos las dependencias. 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | - Es hora de crear una entidad que contenga los detalles del usuario. 18 | 19 | _./model/user-detail.ts_ 20 | 21 | ```typescript 22 | export interface UserDetail { 23 | login: string; 24 | id: number; 25 | avatar_url: string; 26 | name: string; 27 | company: string; 28 | followers: string; 29 | } 30 | 31 | ``` 32 | 33 | - Vamos a crear una nueva entrada en la API para leer los detalles del usuario seleccionado desde la api de github. 34 | 35 | _./rest-api/github.ts_ 36 | 37 | ```diff 38 | import { User } from '../model/user'; 39 | + import { UserDetail } from '../model/user-detail'; 40 | import fetch from 'isomorphic-unfetch'; 41 | 42 | - const baseRoot = 'https://api.github.com/orgs/lemoncode'; 43 | + const baseRoot = 'https://api.github.com'; 44 | - const userCollectionURL = `${baseRoot}/members` 45 | + const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 46 | + const userDetailURL = `${baseRoot}/users`; 47 | 48 | export const fetchUsers = async () => { 49 | const res = await fetch(userCollectionURL) 50 | const data = await res.json(); 51 | 52 | return data.map( 53 | ({ id, login, avatar_url, }) => ({ id, login, avatar_url, } as User) 54 | ); 55 | } 56 | 57 | + export const fetchUserDetail = async (user: string): Promise => { 58 | + const res = await fetch(`${userDetailURL}/${user}`); 59 | + const { id, login, avatar_url, name, company, followers } = await res.json(); 60 | 61 | + return { 62 | + id, 63 | + login, 64 | + avatar_url, 65 | + name, 66 | + company, 67 | + followers, 68 | + }; 69 | + }; 70 | 71 | ``` 72 | 73 | - Ahora que tenemos los datos cargados es hora de mostrarlos en el componente, implementaremos algo muy simple. 74 | 75 | _./pages/user-info.tsx_ 76 | 77 | ```diff 78 | - import { withRouter } from 'next/router'; 79 | + import * as Next from 'next'; 80 | + import { fetchUserDetail } from '../rest-api/github'; 81 | + import { UserDetail } from '../model/user-detail'; 82 | 83 | - const UserInfoPage = withRouter((props) => ( 84 | -
85 | -

I'm the user infopage

86 | -

{props.router.query.login}

87 | -
88 | - )); 89 | 90 | + interface Props { 91 | + login: string; 92 | + userDetail: UserDetail; 93 | + } 94 | 95 | + const UserInfoPage: Next.NextStatelessComponent = props => ( 96 | +
97 | +

I'm the user infopage

98 | +

User ID: {props.userDetail.id}

99 | + 100 | +

User name: {props.login}

101 | +

Company: {props.userDetail.company}

102 | +

Followers: {props.userDetail.followers}

103 | +
104 | + ); 105 | 106 | + UserInfoPage.getInitialProps = async props => { 107 | + const login = props.query.login as string; 108 | + const userDetail = await fetchUserDetail(login); 109 | 110 | + return { 111 | + login, 112 | + userDetail, 113 | + }; 114 | + }; 115 | 116 | export default UserInfoPage; 117 | 118 | ``` 119 | 120 | - Vamos a probarlo: 121 | 122 | ```bash 123 | npm run dev 124 | ``` 125 | -------------------------------------------------------------------------------- /06-detail-page/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import { UserDetail } from '../model/user-detail'; 3 | import fetch from 'isomorphic-unfetch'; 4 | 5 | const baseRoot = 'https://api.github.com'; 6 | const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 7 | const userDetailURL = `${baseRoot}/users`; 8 | 9 | export const fetchUsers = async (): Promise => { 10 | const res = await fetch(userCollectionURL); 11 | const data = await res.json(); 12 | 13 | return data.map( 14 | ({ id, login, avatar_url }) => ({ id, login, avatar_url } as User) 15 | ); 16 | }; 17 | 18 | export const fetchUserDetail = async (user: string): Promise => { 19 | const res = await fetch(`${userDetailURL}/${user}`); 20 | const { id, login, avatar_url, name, company, followers } = await res.json(); 21 | 22 | return { 23 | id, 24 | login, 25 | avatar_url, 26 | name, 27 | company, 28 | followers, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /06-detail-page/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | 4 | const dev = process.env.NODE_ENV !== 'production'; 5 | const app = next({ dev }); 6 | const handler = app.getRequestHandler(); 7 | 8 | app 9 | .prepare() 10 | .then(() => { 11 | const server = express(); 12 | 13 | server.get('/user-info/login/:login', (req, res) => { 14 | return app.render(req, res, '/user-info', { login: req.params.login }); 15 | }); 16 | 17 | server.get('*', (req, res) => { 18 | return handler(req, res); 19 | }); 20 | 21 | server.listen(3000, err => { 22 | if (err) throw err; 23 | console.log('> Ready on http://localhost:3000'); 24 | }); 25 | }) 26 | .catch(ex => { 27 | console.error(ex.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /06-detail-page/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /07-styles/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /07-styles/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /07-styles/components/users/header.css: -------------------------------------------------------------------------------- 1 | .purple-box { 2 | border: 2px dotted purple; 3 | } 4 | 5 | .blue-box { 6 | border: 3px solid blue; 7 | } 8 | -------------------------------------------------------------------------------- /07-styles/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | const styles = require('./header.css'); 2 | 3 | export const Header = () => ( 4 |
5 | 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /07-styles/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /07-styles/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /07-styles/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
AvatarIdName
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /07-styles/model/user-detail.ts: -------------------------------------------------------------------------------- 1 | export interface UserDetail { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | name: string; 6 | company: string; 7 | followers: string; 8 | } 9 | -------------------------------------------------------------------------------- /07-styles/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /07-styles/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withTypescript( 5 | withCSS({ 6 | cssModules: true, 7 | cssLoaderOptions: { 8 | camelCase: true, 9 | importLoaders: 1, 10 | localIdentName: '[local]___[hash:base64:5]', 11 | }, 12 | }) 13 | ); 14 | -------------------------------------------------------------------------------- /07-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-css": "^1.0.1", 14 | "@zeit/next-typescript": "^1.1.1", 15 | "express": "^4.16.4", 16 | "isomorphic-unfetch": "^3.0.0", 17 | "next": "^8.0.4", 18 | "react": "^16.8.6", 19 | "react-dom": "^16.8.6" 20 | }, 21 | "devDependencies": { 22 | "@types/next": "^8.0.3", 23 | "@types/react": "^16.8.13", 24 | "typescript": "^3.4.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /07-styles/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /07-styles/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { fetchUserDetail } from '../rest-api/github'; 3 | import { UserDetail } from '../model/user-detail'; 4 | 5 | interface Props { 6 | login: string; 7 | userDetail: UserDetail; 8 | } 9 | 10 | const UserInfoPage: Next.NextStatelessComponent = props => ( 11 |
12 |

I'm the user infopage

13 |

User ID: {props.userDetail.id}

14 | 15 |

User name: {props.login}

16 |

Company: {props.userDetail.company}

17 |

Followers: {props.userDetail.followers}

18 |
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 | - 54 | + 55 | 56 | 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 | 138 | - 139 | + 140 | 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 | - 182 | + 183 | 184 | 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 | - 282 | + 283 | - 284 | + 285 | 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 | - 302 | + 303 |
AvatarAvatarIdName
AvatarIdIdName
AvatarAvatarIdName
AvatarAvatarIdIdName
304 | ... 305 | 306 | ``` 307 | 308 | _./components/users/table.tsx_ 309 | 310 | ```diff 311 | import * as Next from 'next'; 312 | import { User } from '../../model/user'; 313 | import { Header } from './header'; 314 | import { Row } from './row'; 315 | + const styles = require('./table.css'); 316 | 317 | ... 318 | 319 | export const Table: Next.NextStatelessComponent = (props) => ( 320 | - 321 | +
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 | - 54 | + 55 | 56 | 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 | 138 | - 139 | + 140 | 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 | - 182 | + 183 | 184 | 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 | - 280 | + 281 | - 282 | + 283 | 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 | - 300 | + 301 |
AvatarAvatarIdName
AvatarIdIdName
AvatarAvatarIdName
AvatarAvatarIdIdName
302 | ... 303 | 304 | ``` 305 | 306 | _./components/users/table.tsx_ 307 | 308 | ```diff 309 | import * as Next from 'next'; 310 | import { User } from '../../model/user'; 311 | import { Header } from './header'; 312 | import { Row } from './row'; 313 | + const styles = require('./table.css'); 314 | 315 | ... 316 | 317 | export const Table: Next.NextStatelessComponent = (props) => ( 318 | - 319 | +
320 | ... 321 | 322 | ``` 323 | -------------------------------------------------------------------------------- /07-styles/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import { UserDetail } from '../model/user-detail'; 3 | import fetch from 'isomorphic-unfetch'; 4 | 5 | const baseRoot = 'https://api.github.com'; 6 | const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 7 | const userDetailURL = `${baseRoot}/users`; 8 | 9 | export const fetchUsers = async (): Promise => { 10 | const res = await fetch(userCollectionURL); 11 | const data = await res.json(); 12 | 13 | return data.map( 14 | ({ id, login, avatar_url }) => ({ id, login, avatar_url } as User) 15 | ); 16 | }; 17 | 18 | export const fetchUserDetail = async (user: string): Promise => { 19 | const res = await fetch(`${userDetailURL}/${user}`); 20 | const { id, login, avatar_url, name, company, followers } = await res.json(); 21 | 22 | return { 23 | id, 24 | login, 25 | avatar_url, 26 | name, 27 | company, 28 | followers, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /07-styles/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | 4 | const dev = process.env.NODE_ENV !== 'production'; 5 | const app = next({ dev }); 6 | const handler = app.getRequestHandler(); 7 | 8 | app 9 | .prepare() 10 | .then(() => { 11 | const server = express(); 12 | 13 | server.get('/user-info/login/:login', (req, res) => { 14 | return app.render(req, res, '/user-info', { login: req.params.login }); 15 | }); 16 | 17 | server.get('*', (req, res) => { 18 | return handler(req, res); 19 | }); 20 | 21 | server.listen(3000, err => { 22 | if (err) throw err; 23 | console.log('> Ready on http://localhost:3000'); 24 | }); 25 | }) 26 | .catch(ex => { 27 | console.error(ex.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /07-styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /08-serverless/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /08-serverless/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /08-serverless/components/users/header.css: -------------------------------------------------------------------------------- 1 | .purple-box { 2 | border: 2px dotted purple; 3 | } 4 | 5 | .blue-box { 6 | border: 3px solid blue; 7 | } 8 | -------------------------------------------------------------------------------- /08-serverless/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | const styles = require('./header.css'); 2 | 3 | export const Header = () => ( 4 | 5 | 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /08-serverless/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /08-serverless/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /08-serverless/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
AvatarIdName
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /08-serverless/model/user-detail.ts: -------------------------------------------------------------------------------- 1 | export interface UserDetail { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | name: string; 6 | company: string; 7 | followers: string; 8 | } 9 | -------------------------------------------------------------------------------- /08-serverless/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /08-serverless/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withTypescript( 5 | withCSS({ 6 | cssModules: true, 7 | cssLoaderOptions: { 8 | camelCase: true, 9 | importLoaders: 1, 10 | localIdentName: '[local]___[hash:base64:5]', 11 | }, 12 | target: 'serverless', 13 | }) 14 | ); 15 | -------------------------------------------------------------------------------- /08-serverless/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "test-now-deploy", 4 | "builds": [{ "src": "next.config.js", "use": "@now/next" }], 5 | "routes": [{ "src": "/user-info/login/(?[^/]+)", "dest": "/user-info?login=$login" }] 6 | } 7 | -------------------------------------------------------------------------------- /08-serverless/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zeit/next-css": "^1.0.1", 14 | "@zeit/next-typescript": "^1.1.1", 15 | "express": "^4.16.4", 16 | "isomorphic-unfetch": "^3.0.0", 17 | "next": "^8.1.0", 18 | "react": "^16.8.6", 19 | "react-dom": "^16.8.6" 20 | }, 21 | "devDependencies": { 22 | "@types/next": "^8.0.3", 23 | "@types/react": "^16.8.13", 24 | "typescript": "^3.4.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /08-serverless/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /08-serverless/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { fetchUserDetail } from '../rest-api/github'; 3 | import { UserDetail } from '../model/user-detail'; 4 | 5 | interface Props { 6 | login: string; 7 | userDetail: UserDetail; 8 | } 9 | 10 | const UserInfoPage: Next.NextStatelessComponent = props => ( 11 |
12 |

I'm the user infopage

13 |

User ID: {props.userDetail.id}

14 | 15 |

User name: {props.login}

16 |

Company: {props.userDetail.company}

17 |

Followers: {props.userDetail.followers}

18 |
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 | -------------------------------------------------------------------------------- /08-serverless/readme.md: -------------------------------------------------------------------------------- 1 | # Serverless 2 | 3 | In this example we are going to add necessary config for create a serverless app. 4 | 5 | # Steps 6 | 7 | - Let's start by copying the content of _07-styles_ in our working folder. 8 | 9 | - Let's install the needed packages. 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | - Create a new repository in [github](https://github.com) or [gitlab](https://gitlab.com). 16 | 17 | - Create a free account in [zeit.co/now](https://zeit.co/now). 18 | 19 | - Upload our app to repository. 20 | 21 | - In order to enable `serverless` mode, we need to update our `next.config.js` file: 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 | withCSS({ 31 | cssModules: true, 32 | cssLoaderOptions: { 33 | camelCase: true, 34 | importLoaders: 1, 35 | localIdentName: '[local]___[hash:base64:5]', 36 | }, 37 | + target: 'serverless', 38 | }) 39 | ); 40 | 41 | ``` 42 | 43 | - Add `now` configuration file, [more docs](https://zeit.co/docs/v2/deployments/configuration): 44 | 45 | _./now.json_ 46 | 47 | ```json 48 | { 49 | "version": 2, 50 | "name": "test-now-deploy", 51 | "builds": [{ "src": "next.config.js", "use": "@now/next" }] 52 | } 53 | 54 | ``` 55 | 56 | - Git commit to repository. 57 | 58 | - Now, our app was deployed at `https://test-now-deploy-59lyhaf1v.now.sh/` or similar url. 59 | 60 | - Notice that it's not working the `friendly-url` due to we are not using `server.js` file when build app. We need to configure `now.json` to resolve routes: 61 | 62 | _./now.json_ 63 | 64 | ```diff 65 | { 66 | "version": 2, 67 | "name": "test-now-deploy", 68 | - "builds": [{ "src": "next.config.js", "use": "@now/next" }] 69 | + "builds": [{ "src": "next.config.js", "use": "@now/next" }], 70 | + "routes": [{ "src": "/user-info/login/(?[^/]+)", "dest": "/user-info?login=$login" }] 71 | } 72 | 73 | ``` 74 | 75 | > NOTE: Previous configuration only works for `now` cloud, not in local mode. 76 | 77 | - Commit changes to repository and it will be auto deployed to `now` cloud. 78 | -------------------------------------------------------------------------------- /08-serverless/readme_es.md: -------------------------------------------------------------------------------- 1 | # Serverless 2 | 3 | En este ejemplo vamos a añadir la configuración necesaria para crear una aplicación serverless. 4 | 5 | # Steps 6 | 7 | - Copiamos el contenido del ejemplo _07-styles_ . 8 | 9 | - Vamos a instalar los paquetes necesarios. 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | - Creamos un nuevo repositorio en [github](https://github.com) or [gitlab](https://gitlab.com). 16 | 17 | - Creamos una cuenta gratuita en [zeit.co/now](https://zeit.co/now). 18 | 19 | - Subimos la aplicación en el repositorio. 20 | 21 | - Para habilitar el modo `serverless`, necesitamos actualizar nuestro fichero `next.config.js`: 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 | withCSS({ 31 | cssModules: true, 32 | cssLoaderOptions: { 33 | camelCase: true, 34 | importLoaders: 1, 35 | localIdentName: '[local]___[hash:base64:5]', 36 | }, 37 | + target: 'serverless', 38 | }) 39 | ); 40 | 41 | ``` 42 | 43 | - Añadimos el fichero de configuración `now`, [más info](https://zeit.co/docs/v2/deployments/configuration): 44 | 45 | _./now.json_ 46 | 47 | ```json 48 | { 49 | "version": 2, 50 | "name": "test-now-deploy", 51 | "builds": [{ "src": "next.config.js", "use": "@now/next" }] 52 | } 53 | 54 | ``` 55 | 56 | - Hacemos un git commit al repositorio. 57 | 58 | - Ya tenemos desplegada nuestra aplicación en `https://test-now-deploy-59lyhaf1v.now.sh/` or en una url similar. 59 | 60 | - Importante, no está funcionando las `friendly-url` debido a que el now.json no está usando el fichero `server.js` cuando compila la aplicación. Para solucionarlo, necesitamos configurar las rutas en el fichero `now.json`: 61 | 62 | _./now.json_ 63 | 64 | ```diff 65 | { 66 | "version": 2, 67 | "name": "test-now-deploy", 68 | - "builds": [{ "src": "next.config.js", "use": "@now/next" }] 69 | + "builds": [{ "src": "next.config.js", "use": "@now/next" }], 70 | + "routes": [{ "src": "/user-info/login/(?[^/]+)", "dest": "/user-info?login=$login" }] 71 | } 72 | 73 | ``` 74 | 75 | > NOTA: Esta configuración solamente funciona para la cloud `now`, no funciona en local. 76 | 77 | - Hacemos commit al repositorio y se auto desplegará el código en la cloud `now`. 78 | -------------------------------------------------------------------------------- /08-serverless/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import { UserDetail } from '../model/user-detail'; 3 | import fetch from 'isomorphic-unfetch'; 4 | 5 | const baseRoot = 'https://api.github.com'; 6 | const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 7 | const userDetailURL = `${baseRoot}/users`; 8 | 9 | export const fetchUsers = async (): Promise => { 10 | const res = await fetch(userCollectionURL); 11 | const data = await res.json(); 12 | 13 | return data.map( 14 | ({ id, login, avatar_url }) => ({ id, login, avatar_url } as User) 15 | ); 16 | }; 17 | 18 | export const fetchUserDetail = async (user: string): Promise => { 19 | const res = await fetch(`${userDetailURL}/${user}`); 20 | const { id, login, avatar_url, name, company, followers } = await res.json(); 21 | 22 | return { 23 | id, 24 | login, 25 | avatar_url, 26 | name, 27 | company, 28 | followers, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /08-serverless/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | 4 | const dev = process.env.NODE_ENV !== 'production'; 5 | const app = next({ dev }); 6 | const handler = app.getRequestHandler(); 7 | 8 | app 9 | .prepare() 10 | .then(() => { 11 | const server = express(); 12 | 13 | server.get('/user-info/login/:login', (req, res) => { 14 | return app.render(req, res, '/user-info', { login: req.params.login }); 15 | }); 16 | 17 | server.get('*', (req, res) => { 18 | return handler(req, res); 19 | }); 20 | 21 | server.listen(3000, err => { 22 | if (err) throw err; 23 | console.log('> Ready on http://localhost:3000'); 24 | }); 25 | }) 26 | .catch(ex => { 27 | console.error(ex.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /08-serverless/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /09-css-in-js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ], 6 | "plugins": [ 7 | "emotion" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /09-css-in-js/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /09-css-in-js/components/users/header.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Avatar = styled.th` 4 | border: 2px dotted purple; 5 | `; 6 | 7 | export const Id = styled.th` 8 | border: 3px solid blue; 9 | `; 10 | -------------------------------------------------------------------------------- /09-css-in-js/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | import * as s from './header.styles'; 2 | 3 | export const Header = () => ( 4 |
5 | Avatar 6 | Id 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /09-css-in-js/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /09-css-in-js/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /09-css-in-js/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
Name
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /09-css-in-js/model/user-detail.ts: -------------------------------------------------------------------------------- 1 | export interface UserDetail { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | name: string; 6 | company: string; 7 | followers: string; 8 | } 9 | -------------------------------------------------------------------------------- /09-css-in-js/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /09-css-in-js/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withTypescript( 5 | withCSS({ 6 | cssModules: true, 7 | cssLoaderOptions: { 8 | camelCase: true, 9 | importLoaders: 1, 10 | localIdentName: '[local]___[hash:base64:5]', 11 | }, 12 | target: 'serverless', 13 | }) 14 | ); 15 | -------------------------------------------------------------------------------- /09-css-in-js/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "test-now-deploy", 4 | "builds": [{ "src": "next.config.js", "use": "@now/next" }], 5 | "routes": [{ "src": "/user-info/login/(?[^/]+)", "dest": "/user-info?login=$login" }] 6 | } 7 | -------------------------------------------------------------------------------- /09-css-in-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@emotion/core": "^10.0.10", 14 | "@emotion/styled": "^10.0.10", 15 | "@zeit/next-css": "^1.0.1", 16 | "@zeit/next-typescript": "^1.1.1", 17 | "babel-plugin-emotion": "^10.0.9", 18 | "express": "^4.16.4", 19 | "isomorphic-unfetch": "^3.0.0", 20 | "next": "^8.1.0", 21 | "react": "^16.8.6", 22 | "react-dom": "^16.8.6" 23 | }, 24 | "devDependencies": { 25 | "@types/next": "^8.0.3", 26 | "@types/react": "^16.8.13", 27 | "typescript": "^3.4.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /09-css-in-js/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /09-css-in-js/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { fetchUserDetail } from '../rest-api/github'; 3 | import { UserDetail } from '../model/user-detail'; 4 | 5 | interface Props { 6 | login: string; 7 | userDetail: UserDetail; 8 | } 9 | 10 | const UserInfoPage: Next.NextStatelessComponent = props => ( 11 |
12 |

I'm the user infopage

13 |

User ID: {props.userDetail.id}

14 | 15 |

User name: {props.login}

16 |

Company: {props.userDetail.company}

17 |

Followers: {props.userDetail.followers}

18 |
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 | -------------------------------------------------------------------------------- /09-css-in-js/readme.md: -------------------------------------------------------------------------------- 1 | # CSS in JS 2 | 3 | In this example we are going to add necessary config for create an app with css-in-js. 4 | 5 | # Steps 6 | 7 | - Let's start by copying the content of _08-serverless_ in our working folder. 8 | 9 | - Let's install the needed packages. 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | - In order to get ready with `emotion` we will install: 16 | 17 | ```bash 18 | npm install @emotion/core @emotion/styled babel-plugin-emotion --save 19 | ``` 20 | 21 | - Configure `babel-plugin-emotion`: 22 | 23 | _./.babelrc_ 24 | 25 | ```diff 26 | { 27 | "presets": [ 28 | "next/babel", 29 | "@zeit/next-typescript/babel" 30 | - ] 31 | + ], 32 | + "plugins": [ 33 | + "emotion" 34 | + ] 35 | } 36 | 37 | ``` 38 | 39 | - And that is, we will update our components. Rename `header.css` by `header.styles.tsx`: 40 | 41 | _./components/users/header.styles.tsx_ 42 | 43 | ```diff 44 | + import styled from '@emotion/styled'; 45 | 46 | - .purple-box { 47 | + export const Avatar = styled.th` 48 | border: 2px dotted purple; 49 | - } 50 | + `; 51 | 52 | - .blue-box { 53 | + export const Id = styled.th` 54 | border: 3px solid blue; 55 | - } 56 | + `; 57 | 58 | ``` 59 | 60 | - Update `header` component: 61 | 62 | _./components/users/header.tsx_ 63 | 64 | ```diff 65 | - const styles = require('./header.css'); 66 | + import * as s from './header.styles'; 67 | 68 | export const Header = () => ( 69 |
70 | - 71 | + Avatar 72 | - 73 | + Id 74 | 75 | 76 | ); 77 | 78 | ``` 79 | 80 | > NOTE: we could load detail page as first page and then navigate to main page without any issue. 81 | -------------------------------------------------------------------------------- /09-css-in-js/readme_es.md: -------------------------------------------------------------------------------- 1 | # CSS in JS 2 | 3 | In this example we are going to add necessary config for create an app with css-in-js. 4 | 5 | # Steps 6 | 7 | - Let's start by copying the content of _08-serverless_ in our working folder. 8 | 9 | - Let's install the needed packages. 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | -------------------------------------------------------------------------------- /09-css-in-js/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import { UserDetail } from '../model/user-detail'; 3 | import fetch from 'isomorphic-unfetch'; 4 | 5 | const baseRoot = 'https://api.github.com'; 6 | const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 7 | const userDetailURL = `${baseRoot}/users`; 8 | 9 | export const fetchUsers = async (): Promise => { 10 | const res = await fetch(userCollectionURL); 11 | const data = await res.json(); 12 | 13 | return data.map( 14 | ({ id, login, avatar_url }) => ({ id, login, avatar_url } as User) 15 | ); 16 | }; 17 | 18 | export const fetchUserDetail = async (user: string): Promise => { 19 | const res = await fetch(`${userDetailURL}/${user}`); 20 | const { id, login, avatar_url, name, company, followers } = await res.json(); 21 | 22 | return { 23 | id, 24 | login, 25 | avatar_url, 26 | name, 27 | company, 28 | followers, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /09-css-in-js/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | 4 | const dev = process.env.NODE_ENV !== 'production'; 5 | const app = next({ dev }); 6 | const handler = app.getRequestHandler(); 7 | 8 | app 9 | .prepare() 10 | .then(() => { 11 | const server = express(); 12 | 13 | server.get('/user-info/login/:login', (req, res) => { 14 | return app.render(req, res, '/user-info', { login: req.params.login }); 15 | }); 16 | 17 | server.get('*', (req, res) => { 18 | return handler(req, res); 19 | }); 20 | 21 | server.listen(3000, err => { 22 | if (err) throw err; 23 | console.log('> Ready on http://localhost:3000'); 24 | }); 25 | }) 26 | .catch(ex => { 27 | console.error(ex.stack); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /09-css-in-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /10-production-mode/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "next/babel", 4 | "@zeit/next-typescript/babel" 5 | ], 6 | "plugins": [ 7 | "emotion" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /10-production-mode/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | -------------------------------------------------------------------------------- /10-production-mode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Next", 8 | "runtimeExecutable": "${workspaceFolder}\\node_modules\\.bin\\next", 9 | "port": 9229, 10 | "env": { 11 | "NODE_OPTIONS": "--inspect" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /10-production-mode/components/users/header.styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Avatar = styled.th` 4 | border: 2px dotted purple; 5 | `; 6 | 7 | export const Id = styled.th` 8 | border: 3px solid blue; 9 | `; 10 | -------------------------------------------------------------------------------- /10-production-mode/components/users/header.tsx: -------------------------------------------------------------------------------- 1 | import * as s from './header.styles'; 2 | 3 | export const Header = () => ( 4 | 5 | Avatar 6 | Id 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /10-production-mode/components/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table'; 2 | -------------------------------------------------------------------------------- /10-production-mode/components/users/row.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { User } from '../../model/user'; 4 | 5 | interface Props { 6 | user: User; 7 | } 8 | 9 | export const Row: Next.NextStatelessComponent = (props) => ( 10 | 11 | 14 | 17 | 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /10-production-mode/components/users/table.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { User } from '../../model/user'; 3 | import { Header } from './header'; 4 | import { Row } from './row'; 5 | 6 | interface Props { 7 | users: User[]; 8 | } 9 | 10 | export const Table: Next.NextStatelessComponent = (props) => ( 11 |
AvatarIdName
Name
12 | 13 | 15 | {props.user.id} 16 | 18 | 19 | {props.user.login} 20 | 21 |
12 | 13 |
14 |
15 | 16 | { 17 | props.users.map(user => ( 18 | 19 | )) 20 | } 21 | 22 |
23 | ) 24 | -------------------------------------------------------------------------------- /10-production-mode/model/user-detail.ts: -------------------------------------------------------------------------------- 1 | export interface UserDetail { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | name: string; 6 | company: string; 7 | followers: string; 8 | } 9 | -------------------------------------------------------------------------------- /10-production-mode/model/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | login: string; 3 | id: number; 4 | avatar_url: string; 5 | } 6 | -------------------------------------------------------------------------------- /10-production-mode/next.config.js: -------------------------------------------------------------------------------- 1 | const withTypescript = require('@zeit/next-typescript'); 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withTypescript( 5 | withCSS({ 6 | cssModules: true, 7 | cssLoaderOptions: { 8 | camelCase: true, 9 | importLoaders: 1, 10 | localIdentName: '[local]___[hash:base64:5]', 11 | }, 12 | target: 'server', 13 | }) 14 | ); 15 | -------------------------------------------------------------------------------- /10-production-mode/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "test-now-deploy", 4 | "builds": [{ "src": "next.config.js", "use": "@now/next" }], 5 | "routes": [{ "src": "/user-info/login/(?[^/]+)", "dest": "/user-info?login=$login" }] 6 | } 7 | -------------------------------------------------------------------------------- /10-production-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "00-hello-next", 3 | "version": "1.0.0", 4 | "description": "Let's get started with the very basics let's create a very basic 'Hello World' sample using next.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "build": "next build" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@emotion/core": "^10.0.10", 15 | "@emotion/styled": "^10.0.10", 16 | "@zeit/next-css": "^1.0.1", 17 | "@zeit/next-typescript": "^1.1.1", 18 | "babel-plugin-emotion": "^10.0.9", 19 | "dotenv": "^8.0.0", 20 | "express": "^4.16.4", 21 | "isomorphic-unfetch": "^3.0.0", 22 | "next": "^8.1.0", 23 | "react": "^16.8.6", 24 | "react-dom": "^16.8.6" 25 | }, 26 | "devDependencies": { 27 | "@types/next": "^8.0.3", 28 | "@types/react": "^16.8.13", 29 | "typescript": "^3.4.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /10-production-mode/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import Link from 'next/link'; 3 | import { fetchUsers } from '../rest-api/github'; 4 | import { User } from '../model/user'; 5 | import { Table } from '../components/users'; 6 | 7 | interface Props { 8 | users: User[]; 9 | } 10 | 11 | const Index: Next.NextStatelessComponent = (props) => ( 12 |
13 |

Hello Next.js

14 | 15 | 16 | Navigate to user info page 17 | 18 | 19 | ); 20 | 21 | Index.getInitialProps = async () => { 22 | const users = await fetchUsers(); 23 | 24 | return { 25 | users, 26 | } 27 | } 28 | 29 | export default Index; 30 | -------------------------------------------------------------------------------- /10-production-mode/pages/user-info.tsx: -------------------------------------------------------------------------------- 1 | import * as Next from 'next'; 2 | import { fetchUserDetail } from '../rest-api/github'; 3 | import { UserDetail } from '../model/user-detail'; 4 | 5 | interface Props { 6 | login: string; 7 | userDetail: UserDetail; 8 | } 9 | 10 | const UserInfoPage: Next.NextStatelessComponent = props => ( 11 |
12 |

I'm the user infopage

13 |

User ID: {props.userDetail.id}

14 | 15 |

User name: {props.login}

16 |

Company: {props.userDetail.company}

17 |

Followers: {props.userDetail.followers}

18 |
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 | -------------------------------------------------------------------------------- /10-production-mode/readme.md: -------------------------------------------------------------------------------- 1 | # Production mode 2 | 3 | In this example we are going to add necessary config for create an app in prod mode. 4 | 5 | # Steps 6 | 7 | - Let's start by copying the content of _09-css-in-js_ in our working folder. 8 | 9 | - Let's install the needed packages. 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | - Add build step: 16 | 17 | ### ./package.json 18 | 19 | ```diff 20 | ... 21 | "scripts": { 22 | "dev": "node server.js" 23 | + "dev": "node server.js", 24 | + "build": "next build" 25 | }, 26 | ``` 27 | 28 | - Install `dotenv`: 29 | 30 | ```bash 31 | npm install dotenv --save 32 | ``` 33 | 34 | - Add `.env` file: 35 | 36 | ### ./.env 37 | 38 | ``` 39 | NODE_ENV=production 40 | ``` 41 | 42 | - Update `server`: 43 | 44 | ### ./server.js 45 | 46 | ```diff 47 | const express = require('express'); 48 | const next = require('next'); 49 | + require('dotenv').config(); 50 | ``` 51 | 52 | - Update npm script: 53 | 54 | ### ./package.json 55 | 56 | ```diff 57 | ... 58 | "scripts": { 59 | - "dev": "node server.js", 60 | + "start": "node server.js", 61 | "build": "next build" 62 | }, 63 | ``` 64 | 65 | - Update to `target: server`: 66 | 67 | ### ./next.config.js 68 | 69 | ```diff 70 | ... 71 | - target: 'serverless', 72 | + target: 'server', 73 | ``` 74 | -------------------------------------------------------------------------------- /10-production-mode/rest-api/github.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../model/user'; 2 | import { UserDetail } from '../model/user-detail'; 3 | import fetch from 'isomorphic-unfetch'; 4 | 5 | const baseRoot = 'https://api.github.com'; 6 | const userCollectionURL = `${baseRoot}/orgs/lemoncode/members`; 7 | const userDetailURL = `${baseRoot}/users`; 8 | 9 | export const fetchUsers = async (): Promise => { 10 | const res = await fetch(userCollectionURL); 11 | const data = await res.json(); 12 | 13 | return data.map( 14 | ({ id, login, avatar_url }) => ({ id, login, avatar_url } as User) 15 | ); 16 | }; 17 | 18 | export const fetchUserDetail = async (user: string): Promise => { 19 | const res = await fetch(`${userDetailURL}/${user}`); 20 | const { id, login, avatar_url, name, company, followers } = await res.json(); 21 | 22 | return { 23 | id, 24 | login, 25 | avatar_url, 26 | name, 27 | company, 28 | followers, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /10-production-mode/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const next = require('next'); 3 | require('dotenv').config(); 4 | 5 | const dev = process.env.NODE_ENV !== 'production'; 6 | console.log(process.env.NODE_ENV); 7 | const app = next({ dev }); 8 | const handler = app.getRequestHandler(); 9 | 10 | app 11 | .prepare() 12 | .then(() => { 13 | const server = express(); 14 | 15 | server.get('/user-info/login/:login', (req, res) => { 16 | return app.render(req, res, '/user-info', { login: req.params.login }); 17 | }); 18 | 19 | server.get('*', (req, res) => { 20 | return handler(req, res); 21 | }); 22 | 23 | server.listen(3000, err => { 24 | if (err) throw err; 25 | console.log('> Ready on http://localhost:3000'); 26 | }); 27 | }) 28 | .catch(ex => { 29 | console.error(ex.stack); 30 | process.exit(1); 31 | }); 32 | -------------------------------------------------------------------------------- /10-production-mode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "removeComments": false, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "lib": [ 17 | "dom", 18 | "es2016" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lemoncode 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 | # nextjs-typescript-by-sample 2 | Set of guided samples about how to get started with nextjs + TypeScript. 3 | 4 | # About the example application 5 | 6 | We have developed a simple web application that displays a list of github users (fetch from github rest api), and when you click on a given user it navigate to a detail page where you can find detailed information about the selected user. 7 | 8 | Topics covered in this sample: 9 | 10 | - Building a hello wolrd nextjs app. 11 | - Adding typescript support. 12 | - Adding navigation between pages. 13 | - Fetching data from a remote api (cors enabled). 14 | - Navigating to anoterh pages passing parameters via query string. 15 | - Creating clean urls (seo friendly). 16 | - CSS Styling: Thanks to [arp82](https://github.com/arp82) for this great contribution. 17 | 18 | Topics that will be covered in future examples: 19 | 20 | - Debugging. 21 | - Deploying. 22 | - Redux support. 23 | 24 | 25 | # About Basefactor + Lemoncode 26 | 27 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 28 | 29 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 30 | 31 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 32 | 33 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 34 | --------------------------------------------------------------------------------