layout: true class: middle --- class: center, middle # Next.js for Universal JS Remy Sharp // @rem Slide link: **https://rem.io/next-workshop** ```bash $ curl -L https://rem.io/next-workshop-package > package.json; npm install ```
Use
p
to toggle notes.
??? - Install everything - I've included links to prepared code at different stages, but if you're rather code yourself, you're welcome to - The slides and code etc are yours to keep to reference later on. --- exclude: true * 10:00 Introductions _(15m)_ * 10:15 Up and running: application architecture _(60m)_ * 11:15 Break _(15m)_ * 11:30 Extending: custom routing, parameters _(90m)_ * 13:00 Lunch _(60m)_ * 14:00 Connecting: Database backed pages _(1h)_ * 15:00 Break _(30m)_ * 15:30 Testing, building & deploying _(90m)_ * 17:00 End --- class: center .scale-2[![next-white](images/next.svg)] …what is it exactly?! ??? Next is a framework for React…which itself is a (sort of) a framework for working with the DOM… Next is currently v9 and uses React 16. Pros: * (Near) zero config * SSR by default * Code splitting About me * Me: Remy Sharp, JavaScript for … late 90's * Very late to the React game, but a lot has settled now (but have done react for paying clients) * ES6 without build tools (i.e. canary support `import` statements) * I'm a big proponent of simplicity I've used next on side projects as well as client projects. The main big wins, to me, are the simplicity to get going, with the added benefit of SSR, configuration simplicity, code splitting and some very, very good engineers solving difficult problems for me. --- # In the wild [![next-in-production.png](images/in-the-wild.png)](https://spectrum.chat/thread/e425a8b6-c9cb-4cd1-90bb-740fb3bd7541) ??? https://spectrum.chat/thread/e425a8b6-c9cb-4cd1-90bb-740fb3bd7541 --- # Some of your pain points * Webpack * Keeping up * Spaghetti code and logic * It can be time consuming to get a simple app up and running with all the build chain / framework scaffolding required * Duplication of code between front end and back end * Sever side rendering and rehydration * ALL THE CONFIGURATION ??? Also a good few of you wanted to get more understanding of React, do ask as we go along! --- # Zero config 100% start Out of the box, zero configuration, write React, ESNext code, HMR and state remains **without** a reloads. --- .fullscreen[
] ??? Start the video to see the state remaining after a change to the source and the component is reloaded. --- class: top
--- class: center-title # Part 1 * Server side rendering * Routing * Common layout * Static assets --- # Typical setup _(but you've installed from https://rem.io/next-workshop-package)_ ```bash npm init -f npm install --save next react react-dom # or npx create-next-app my-app ``` Then add script to run: ```json { "scripts": { "dev": "next", "build": "next build", "start": "next start" } } ``` ??? npx create gives you more scaffolding --- # Setup #2 1. mkdir ./pages 2. Add ./pages/index.js ```js export default () =>
Welcome!
; ``` Now we're ready. --- # **Tip:** No `import React` required The next.js framework doesn't require the `import React from 'react'` at the top of statement components. Nice for linting and "unused vars" 👍 --- # npm run dev ```bash ❯ npm run dev # or npx next [ wait ] starting the development server ... [ info ] waiting on http://localhost:3000 ... [ ready ] compiled successfully - ready on http://localhost:3000 [ wait ] compiling ... [ ready ] compiled successfully - ready on http://localhost:3000 [ event ] client pings, but there's no entry for page: / [ event ] build page: /next/dist/pages/_error ``` --- # Zero config, SSR by default It's unimpressive, but now you can cURL the URL and you'll get the content: ```bash # Mac users: $ curl http://localhost:3000 | textutil -convert txt -stdin -stdout # Unix users: $ lynx -dump http://localhost:3000 # or just $ curl http://localhost:3000 | grep '
' --color=always ``` Not very interesting, but… --- background-image: url(images/ssr.png) --- # pages = routing system ```text pages/index.js => localhost:3000/ pages/about.js => localhost:3000/about pages/user/index.js => localhost:3000/user pages/user/rem.js => localhost:3000/user/rem pages/user/julie.js => localhost:3000/user/julie ``` _What about `/user/:name`? We'll look at more advanced and dynamic routing later on._ --- Let's make a few other pages. 1. [pages/about.js](https://next-workshop-downloads.isthe.link/1/pages/about.js) 2. [pages/contact.js](https://next-workshop-downloads.isthe.link/1/pages/contact.js) Now let's link them and test client side and server side URLs: ```js export default () => (<>
About
// ... then add some more "HTML" with a link to another page
Contact me
... >); ``` Links work, but… --- class: center, middle ![Full server trip](images/full-server-trip.png) ??? Notice from the network tab, we're doing a full round trip for all the HTML, whereas we might we might want to make an XHR request _just_ for the bits we need next and use the history API. --- Let's make it do client side requests too, making use of the History API: ```js import Link from 'next/link'; // ...
Contact me
; ``` ??? Explain that the properties of `
` are cloned down into the child element. There's also a `prefetch` attribute that we'll look at later. --- ![Works with JS off too](images/js-off.png) ??? You should be able to see that the `href` property is in the anchor element - so works in the client without JavaScript (ie. can be crawled etc). --- We're using the same JavaScript for both server side and client side rendering. The `Link` element can be used to wrap anything too…just be wary: .pull-left[```code
About
```] .pull-right[![gif](images/bad-link.gif)] --- # Segue: linting It's possible might have a few red snakes if you have a linter running: ![linting bad](images/linting.png) --- # Using eslint I'm using VS Code and the following config: https://rem.io/next-lint Save the two files in the root of your project, and add the development dependencies to your code: ``` $ npm install --save-dev \ eslint \ babel-eslint \ eslint-plugin-react ``` ??? TODO check this config --- # Layout Without any configuration, Next is using latest ES6 tech, including modules to import. Let's create some new components and re-use them. --- # Layout The only _special_ and magical directory is the `pages`, otherwise you can use any other name for your modules. 1. mkdir ./components 2. Add [./components/Header.js](https://next-workshop-downloads.isthe.link/1/components/Header.js) 3. Add [./components/Footer.js](https://next-workshop-downloads.isthe.link/1/components/Footer.js) 4. Add [./components/Layout.js](https://next-workshop-downloads.isthe.link/1/components/Layout.js) --- `Layout.js` looks roughly like this: .pull-left[```js // components/Layout.js import Header from './Header'; import Footer from './Footer'; export default ({ children, ...props }) => (
{children}
); ```] .pull-right[```js // pages/index.js import Layout from '../components/Layout'; export default () => (
Welcome
etc...
); ```]
_…and then repeat for each page you've made…_
--- # Sidebar about `({ children })` There's two things happening here: 1. An argument containing some special properties, in particular `children` which is a React element containing the children nodes 2. The argument is be destructured on the fly to create a new variable called `children` --- ```js export default props =>
{props.children}
; ``` Equivalent to: ```js export default ({ children }) =>
{children}
; ``` The `{` and closing `}` is a JSX expression, so the contents is returned to be interpreted by JSX. --- # What about viewport, common styles, etc? Introducing the first _magic_ file (i.e. Next system file): 1. Add [./pages/_document.js](https://next-workshop-downloads.isthe.link/1/pages/_document.js) 2. When you add a new Next system file, you need to restart next manually --- ```js import Document, { Head, Main, NextScript } from 'next/document'; export default class MyDocument extends Document { render() { return (
); } } ``` ??? Only runs server side, so won't execute anything in the browser, so don't try to attach event handlers like `onClick` for instance. --- # Static assets These live in `/public`, a system folder to Next. 1. Make `/public` directory (in the root) 2. Add all the [stylesheets](https://next-workshop-downloads.isthe.link/1/public/) 3. Include `
` to the `_document.js`. Anything can live in this directory, and next will automatically route to this as static content. ??? Until very recently this was `static` was required, but now there's mapping directly into public --- # Error pages There's a few ways to handle errors, we'll look at the basic way now. ![404](images/404.png) --- To add a custom error page, we: 1. Add [/pages/_error.js](https://next-workshop-downloads.isthe.link/1/pages/_error.js) 2. And return custom content. ```js // filename: pages/_error.js export default () =>
Nope.
; ``` _Remember that I you have to restart the `npm run dev` process to get this new system file to be picked up._ --- More complete error handling page: ```js import React from 'react'; export default class Error extends React.Component { static getInitialProps({ res, err }) { const statusCode = res ? res.statusCode : err ? err.statusCode : null; return { statusCode }; } render() { return
{this.props.statusCode}
; } } ``` We'll get into `getInitialProps` in the next part. ??? Sample from [Next.js _error.js](https://github.com/zeit/next.js#custom-error-handling) --- class: center-title middle # Part 2 - CSS in JS / JSX - Loading data into components - Using fetch - Clean URLs - Page transitions --- # Let's add some content… 1. Add [./data/schedule.json](https://next-workshop-downloads.isthe.link/2/data/schedule.json) 2. Add [./pages/index.js](https://next-workshop-downloads.isthe.link/2/pages/index.js) For now we'll hard code the content, but soon we'll get it from an external source. --- class: center ![](images/css-in-js.png) # CSS-in-JS --- Next ships with styled-jsx: scoped CSS 👍, in blatted in JS 👎 so 🤷♀️ ```js
...
``` Add [rem.io/index-styles](https://rem.io/index-styles) to `index.js` inside the `ul` tag. ??? More to be said about CSS-in-JS, but it's a broad topic solved in multiple ways. We'll see later on how we can get better "components" using small bits of config. --- # Fetching data Introducing `getInitialProps` * `static` method * Async support * Return object is the component's props * Only available on pages --- class: top # Functional / Pure component ```js const Page = ({ sessions }) =>
*Page.getInitialProps = ({ … }) => { /* … do some stuff, then */ return { sessions: [ { title: "How to win when you're winning" }, { title: "How to make up great talk titles" }, { title: "How to quit whilst you're ahead" } ]} } ``` --- class: top # Class component ```js class Page extends React.Component() { * static getInitialProps() { /* … do some stuff, then */ return { sessions: [ { title: "How to win when you're winning" }, { title: "How to make up great talk titles" }, { title: "How to quit whilst you're ahead" } ]} } } ``` --- class: top # Class component ```js class Page extends React.Component() { * static async getInitialProps() { // contrived example const res = await SomePromise(); return { sessions: res.sessions }; } } ``` --- # Fetching data In `./pages/index.js`, read `sessions` off the component props, and let's fetch some real data: ```js Page.getInitialProps = async () => { const res = await fetch(`https://ffconf.org/api/event/2018`); const sessions = await res.json(); return { sessions, }; }; ``` **But wait…there's a problem, any ideas?** ??? We need to provide `fetch` on the server side. We'll do that using isomorphic-unfetch --- Let's add the session detail pages: 1. Add [./pages/session.js](https://next-workshop-downloads.isthe.link/2/pages/session.js) 2. Click around - make sure it works … oh, `mentoring` is hard coded! --- # Reading query strings `getInitialProps` context argument object * `req` - HTTP request object **(server only)** * `res` - HTTP response object **(server only)** *
`query` - query string section of URL parsed as an object
* `pathname` - path section of URL * `asPath` - String of the actual path (including the query) shows in the browser * `err` - Error object if any error is encountered during the rendering _Note: these are properties on a single argument_ ??? Note that only the page component gets the `context` property by default, and if you want your components to receive them, you need to pass them along. --- # Reading query strings In `./pages/session.js`: ```diff -Page.getInitialProps = async () => { +Page.getInitialProps = async ({ query }) => { - const res = await fetch(`https://ffconf.org/api/session/mentoring`); + const res = await fetch(`https://ffconf.org/api/session/${query.slug}`); const session = await res.json(); return { session, }; }; ``` --- # Clean URLs /session?slug=mentoring 😟
/session/mentoring 👍 ![mentoring-query.png](images/mentoring-query.png) --- # Clean URLs in the client ```js
``` ![images/mentoring-clean.png](images/mentoring-clean.png) --- class: middle # Server different story .pull-right[![images/clean-url-404.png](images/clean-url-404.png)] .pull-left[ ```bash ❯ curl http://localhost:3000/session/memory HTTP/1.1 404 Not Found Content-Type: text/html HomeAboutContact Nope 😢 This page could not be found. © Don't steal! Share ❤️ ```] --- class: old-way # Adding server side support 1. Make `./lib` directory 2. Add [./lib/server.js](https://next-workshop-downloads.isthe.link/2/lib/server.js) 3. Add custom handler for our _clean_ url 4. Run with `npm run server` ```js server.get('/session/:slug', (req, res) => { const { slug } = req.params; // render: request, response, path, query object app.render(req, res, '/session', { slug }); }); ``` ??? Note that we'll lose the query string if it's included --- class: old-way # Gotcha: query strings ![query-string-working.png](images/query-string-working.png) --- class: old-way But additional query parameters will be swallowed, because the server request handler didn't pass the query through. Let's fix that: ```js server.get('/session/:slug', (req, res) => { const { slug } = req.params; // render: request, response, path, query object * app.render(req, res, '/session', { ...req.query, slug }); }); ``` --- class: old-way # **Tip:** avoid reload of server.js * Don't use tools like [nodemon](https://nodemon.io) to watch server.js * Develop a separate API endpoint instead (I made [express-router-cli](https://www.npmjs.com/package/express-router-cli) to help with this) * Final deploy can merge backend or be kept separate ??? Why? Because the boot time for Next's and the associated initiation is fairly lengthy - in the seconds, and all those seconds wasted whilst the server reloads is time you're blocked, and it all adds up (in my case: to frustration). --- # Native dynamic routing ```text pages/session/[slug].js => localhost:3000/session/:slug pages/[user]/posts/[id].js => localhost:3000/:user/posts/:id ``` --- # ...in components ```js import { useRouter } from 'next/router'; const Session = () => { const router = useRouter(); const { slug } = router.query; return
Session: {slug}
; } export default Session; ``` --- Now `Link` elements become really quite simple: ```jsx
{session.title}
``` --- # Small restructure 1. mkdir pages/session 2. Rename `pages/session.js` to [pages/session/[slug].js]() 3. No "custom server" code required 🎉 ??? In fact, Next.js is supporting patterns that users needed, so behind the scenes they're doing the work for you. --- # APIs without custom server logic Add a _special_ `pages/api` directory and now: ```text pages/api/index.js => localhost:3000/api pages/api/session/[slug].js => localhost:3000/api/session/:slug ``` Except, script is treated as route handler – --- .pull-left[```js // pages/api/session/[slug].js export default (req, res) => { const { query: { slug }, } = req; const reply = require(`../../../data/${slug}.json`); res.status(200).json(reply); }; ```] .pull-right[API middleware & helpers: - req.cookies - req.query - req.body - res.status(code) - res.json(object) - res.send(any)
[Download raw "database"](https://next-workshop-downloads.isthe.link/2/data.zip) and unzip into `./data/` ]
??? [Find out more](https://github.com/zeit/next.js/#api-routes) --- # We can now develop our own API Let's swap out `ffconf.org` for `localhost:3000` in `getInitialProps`. ??? Let's test all that. --- ```js Page.getInitialProps = async ({ query }) => { const res = await fetch(`http://localhost:3000/api/session/${query.slug}`); const session = await res.json(); return { session, }; }; ``` Spot the _new_ problem though? ??? Problem here? All the Next.js examples use this particular code, but when you deploy, localhost:3000 won't resolve. --- # Environment variables Secret keys, dev settings and more. ```bash # dev API=http://localhost:3000/api npm run dev ``` ```js const res = await fetch(`${process.env.API}/session/${query.slug}`); ``` ??? It's solved using a next.config.js file. --- .center[
] ??? Reason: the client doesn't know about `process` --- # Introducing: next.config.js > But you said it was configuration free! ...and a _sprinkle_ of webpack configuration is needed ??? Lives in the root of the project, will require a hard restart and when we deploy to production we can set this env value in the project which will override the value we've set here. --- # Solution: [next.config.js](https://next-workshop-downloads.isthe.link/2/next.config.js) ```js const webpack = require('webpack'); module.exports = { webpack: config => { config.plugins.push( new webpack.EnvironmentPlugin({ API: 'http://localhost:3000/api', // set default }) ); return config; }, }; ``` Converts all instances of `process.env.API` to the given value. --- # Restart and have a test 🏗 --- # Custom page transitions We can hook into the routing Next uses. In `./components/Layout.js` (https://rem.io/nprogress) add: ```js import router from 'next/router'; import NProgress from 'nprogress'; router.onRouteChangeStart = () => NProgress.start(); router.onRouteChangeComplete = () => NProgress.done(); router.onRouteChangeError = () => NProgress.done(); ``` We're using the NProgress loading bar to transition between pages. You can hook into and even cancel routing lifecycle. --- # Link Prefetching * Add `prefetch` attribute to `
` elements * Runs in the client so link target loads immediately * Also has using imperative API: * **Only runs in production** ```react import router from 'next/router'; // ...in the component
{ router.prefetch('/about'); }}>About
``` ??? Note that prefetch only works only in production, that's to say: only after `next build` --- # Test gotcha: prefetch throws ![prefetch-workaround.png](images/prefetch-workaround.png) --- # Recap * JSX available for CSS-in-JS out of the box * `async getIntialProps` static method to inject props * Using `fetch` 👍 but remember to add to server side * `
` for clean URLs * Clean URLs achieved with `[param].js` naming convention * API out of the box via `pages/api` * `process.env` needs exposing via `next.config.js` --- class: center-title # Part 3 * Authentication * Common top level component --- # 🔒 Authentication --- [![auth](images/auth.png)](https://github.com/zeit/next.js/issues/153) --- .omg[![auth](images/omg-max.png)] --- class: center # What's required? 🤔 🙋 .left[
Any ideas or suggestions?
[ideas please]
] ??? * Log in page * End point to auth against (github, our own etc) * We can reuse auth on cookie if it's an API * We should store a token if it's --- # Let's login And yes, I did throw in some [`useState`](https://reactjs.org/docs/hooks-intro.html) for fun and befuddlement. 1. Add [./pages/login.js](https://next-workshop-downloads.isthe.link/3/pages/login.js) 2. Add [./pages/api/user/index.js](https://next-workshop-downloads.isthe.link/3/pages/api/user/index.js) - to handle `POST` and `GET` auth requests 3. Add [./lib/withUser.js](https://next-workshop-downloads.isthe.link/3/lib/withUser.js) - this is a
HOC
that we'll add to out pages/* Let's take a look at the code briefly… --- # Add withUser HOC Add to `./pages/index.js` at the top: ```js import { withUser } from '../lib/withUser'; const Page = ({ user }) => (
``` And bottom: ```diff -export default Page +export default withUser(Page) ``` --- class: center # So, sign in works…right? ??? Ideas? --- .fullscreen[
] --- # **Problem:** only authed in client Which works with fetch in the client…but not the server. In `./lib/withUser.js` ```js fetch(`${API}/user`, { credentials: 'include', // what does this mean? }); ``` Any idea what's wrong? ??? Also wouldn't work if the sever was on the same origin --- # Server 🍪🍪🍪🍪🍪🍪 ```js const isServer = typeof window === 'undefined'; const headers = {}; *if (isServer) headers.cookie = req.headers.cookie; fetch(`${API}/user`, { headers, credentials: 'include', }); ``` ??? No, because server still doesn't have access to cookies. --- # Note: across origins, use `authorization` _Not needed for our workshop, but useful to know_ From https://rem.io/next-auth-token we can: * Capture our auth token and store in a client cookie * Update `./lib/withUser.js` to use `authorization` header * …and read the cookie. --- class: top ```js function getToken({ req, token }) { if (token) { return token; } if (!req) { // we're client side const cookie = require('js-cookie'); return cookie.get('token'); } else { // find it on the server } } ``` --- class: top ```js function getToken({ req, token }) { if (token) { return token; } if (!req) { // we're client side const cookie = require('js-cookie'); return cookie.get('token'); } else { // find it on the server const cookie = require('cookie'); * const { token } = cookie.parse(req.headers.cookie); return token; } } ``` --- class: center cover white bottom background-image: url(images/wat.gif) # Add withUser to all pages? # (╯°□°)╯︵ ┻━┻ --- # Custom `
` Next.js uses the `App` component to initialize pages. You can override it and control the page initialization. Which allows you to do amazing things like: * Persisting layout between page changes * Keeping state when navigating pages * Custom error handling using `componentDidCatch` * Inject additional data into pages (for example by processing GraphQL queries) [Via Next Docs](https://nextjs.org/docs/#custom-app) --- # Persist layout _and_ user 1. Add [./pages/_app.js](https://next-workshop-downloads.isthe.link/3/pages/_app.js) 2. Change your `./pages/*` files and remove `import Layout…` & `
` wrapper 3. Restart your server 4. User will be propagated down the components Layout is now always included, and user is persisted across pages. ??? Worth also reviewing the App Container --- # Recap * Auth: always think about **both** client and server * I prefer to use a token in `authorization` header * Cookies is the only shareable storage * `_app.js` is a powerup for your pages --- class: center-title # Part 4 * Dynamic modules * Plugins * Polyfills * Static export & tricks --- # Dynamic / `import()` Download [./components/Notes.js](https://next-workshop-downloads.isthe.link/4/components/Notes.js) and add to `./pages/session/[slug].js`: ```js import dynamic from 'next/dynamic'; const Notes = dynamic(() => import('../components/Notes.js')); ``` Add the following after the _last_ ``: ```js {user &&
} ``` Then add [pages/api/user/notes/[slug].js](https://next-workshop-downloads.isthe.link/4/pages/api/user/notes/[slug].js) ??? Note: we also have to include the API endpoint --- # Dynamic / `import()` Because `Notes` is conditionally rendered, it isn't downloaded at all until it's needed (code splitting 🤘). You only see the log below when the module is imported: ``` 🏗️ Notes component has been imported ``` Logged out users don't need that code, so we don't force them to download it. --- # Plugins Next [plugins](https://github.com/zeit/next-plugins) easily add extra tooling features, like SCSS or Less, or Preact, etc. --- class: top # Plugins: MDX Markdown in JSX 🤔🤯 In [next.config.js](https://next-workshop-downloads.isthe.link/4/next.config.js), add: ```js const withMDX = require('@zeit/next-mdx')({ extension: /\.mdx?$/, }); module.exports = withMDX({ pageExtensions: ['js', 'jsx', 'mdx', 'md'], // I like to add .md to my list which allows for 100% pure markdown files // …here goes the rest of your current config object }) ``` --- class: top # Plugins: MDX Now create a markdown based file in [./pages/aboot.md](https://next-workshop-downloads.isthe.link/4/pages/aboot.md) and navigate to `/aboot` - it'll render. _(We can also use `.mdx` which supports JSX inside of Markdown 🤯)_ Anyone spot the _small_ issue though? --
--- class: top # Plugins: MDX Missing the `
` element, but we can fix this in `_app.js`: ```js import Link from 'next/link'; import { MDXProvider } from '@mdx-js/react'; const components = { a: ({ children, href, ...props }) => (
{children}
), }; ``` --- class: top # Plugins: MDX And `render` becomes: ```js render() { const { Component, pageProps } = this.props; return ( *
*
); } ``` --- # Plugins: bundle analyser A must for your fine tuning. ![](images/analyse.png) ??? BUNDLE_ANALYZE=both npm run build Download the [next.config.js](https://next-workshop-downloads.isthe.link/5/next.config.js) --- # Plugins Interesting others: * Offline (via workbox - seems to "just" drop in) * SCSS/Sass * Preact (great footprint) * Plus your own https://github.com/zeit/next-plugins --- # Polyfills TL;DR use polyfill.io Don't bother with the `withPolyfills` example in Next repo - polyfill.io is added to `_document.js` and we can forget about it. ```html ``` --- # Component directories Do you prefer your components all in a single directory? Tests, storybook, styles all live in that single dir? ![](images/single-dir.png) I like my CSS in CSS files… --- ## Styled JSX in separate files & scoped ```js import styles from './sessions.css'; const Sessions = ({ sessions }) => (
); ``` --- ## Styled JSX in separate files & scoped ```js webpack: (config, { defaultLoaders }) => { config.module.rules.push({ test: /\.css$/, use: [ defaultLoaders.babel, { loader: require('styled-jsx/webpack').loader, options: { type: 'scoped' } }, ], }); // continue as normal ``` --- # Deployment 🚀 Two options: 1. Dynamic node backed _"as it comes"_ 2. Static build ala SPA Depends entirely on your needs. --- # Dynamic Requires node at the backend. Targets I've used (with varying success!): * Zeit's own [now](https://zeit.co/now) * Heroku * [Up](https://github.com/apex/up-examples/tree/master/oss/node-next) (like Now on AWS) * Dokku (for [DigitalOcean](https://www.digitalocean.com/products/one-click-apps/dokku/), AWS, etc) - _Herokuish_ * AWS DIY * Azure `"target": "serverless"` will give you smaller executables, but so far as I know, only Zeit is going to deploy it. ??? As if 2019-10-13 I was trying to deploy target: serverless to Azure and had to give up. See low level here: https://nextjs.org/docs#one-level-lower --- class: top # Github to Circle-CI to Now ```yaml # filename: circle-ci.yml deployment: production: branch: master commands: - now -t=${NOW_TOKEN} && now -t=${NOW_TOKEN} alias next-workshop.isthe.link dependencies: pre: - npm install -g now machine: node: version: 8 ``` Circle-CI is nice because you can have free private github repos (useful for clients). ??? Example: https://circleci.com/gh/remy/next-training/4 (though not a public URL) --- class: top # Github to Travis to Heroku .pull-left.scale-down[```yaml # filename: .travis.yml sudo: false language: node_js cache: directories: - node_modules node_js: - 8 deploy: skip_cleanup: true provider: heroku api_key: secure:
app: my-heroku-app-name on: repo: my-username/my-repo ```] .pull-right.scale-down[```yaml # …continued before_script: - npm prune after_success: - npm run build branches: except: - /^v\d+\.\d+\.\d+$/ ```] Disclaimer: this is my travis.yml from my own node backed blog. It should work, but your mileage may vary! --- # Static deployments Generate a fully static code base. Super useful for static sites (like a conference schedule!), or an SPA. --- In [next.config.js](https://next-workshop-downloads.isthe.link/5/next.config.js) ```js exportPathMap: () => { return { // then return keyed by URL to mapped page '/': { page: '/' }, '/about': { page: '/about' }, '/aboot': { page: '/aboot' }, '/contact': { page: '/contact' }, '/session/mentoring': { page: '/session/[slug]', query: { slug: 'mentoring' } } '/session/web-animation': { page: '/session/[slug]', query: { slug: 'web-animation' } } // etc }; } ``` --- ![static-build-output.png](images/static-build.png) --- The `exportPathMap` can be dynamically generated too: .scale-down[```js exportPathMap: async () => { const json = require('./data/schedule.json'); const sessions = json.reduce( (acc, { slug }) => ({ [`/session/${slug}`]: { page: '/session/[slug]', query { slug } }, ...acc, }), {} ); return { '/': { page: '/' }, '/about': { page: '/about' }, '/aboot': { page: '/aboot' }, '/contact': { page: '/contact' }, ...sessions, }; } ```] ??? But watch out - when you list the pages, if you miss anything, it won't be included - see "login" missing. --- # Next without Next? Hat tip to [Giulia Alfonsi](https://github.com/electricg). Specifically: generate a fully static site with _no_ JavaScript. Requires: * Custom `Head`, `NextScript`, `Main` * Added to [./pages/_document.js](https://next-workshop-downloads.isthe.link/4/pages/_document.js) * Add the [NoNext](https://next-workshop-downloads.isthe.link/4/components/NoNext.zip) `./components/NoNext` Then run `NODE_ENV=production npm run build` --- background-image: url(images/no-next.png) class: contain --- # Final thoughts or questions 🤔 --- class: center # **What will you build next?** [fin] ??? All the code from this project can be [downloaded here](https://next-workshop-downloads.isthe.link/final-project.zip) - [ffconf 2019 final code](https://ffconf-2019.now.sh) - [Smashingconf 2019 final code](https://smashingconf-2019.now.sh)