React Framework - Next.js
About
Next is a React framework for server-rendered React applications.
isomorphic apps (code that runs in the browser or in node)
note from the getting started guide of Next.js
Features
Steps
Project Init
- Create the repository
mkdir hello-next
cd hello-next
- init - Create a new app with create-next-app (yarn under the hood) in the current directory
npm init next-app .
Pick a template » - Use arrow-keys. Return to submit.
√ Pick a template » Default starter app
Creating a new Next.js app in C:\code\hello-next.
Installing react, react-dom, and next using yarn...
The only special directory is the pages directory.
Next Package.json Script
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
Start Dev Server
npm run dev
Page
Next.js is all about pages. We can create a page by exporting a React component, and putting that component inside the pages directory. Then it will have a fixed URL based on the file name.
The only special directory is the pages directory.
A page:
- importing a custom component
- and exporting an other React component (ie export default
import Link from 'next/link'
import Header from '../components/Header'
export default function Index() {
return (
<div>
<Header />
<p>Hello Next.js</p>
</div>
)
}
Component
Use cases for shared components:
- As common components.
- As Layouts.
Common Component
A component directory can be named anything (by convention components), the only special directory is the pages directory. You can even create the Component inside the pages directory if you need a direct URL to your component.
- a Header component with a next link
import Link from 'next/link'
const linkStyle = {
marginRight: 15
}
const Header = () => (
<div>
<Link href="/">
<a style={linkStyle}>Home</a>
</Link>
<Link href="/about">
<a style={linkStyle}>About</a>
</Link>
</div>
)
export default Header
Layout
see React - Composite Component (Layout) / Container Component
Navigations
- client side navigations: link
Link
Navigation / Routing
- client-side navigation: Link API, which is exported via next/link (handle the location.history of the browser)
- next/link is a higher order component which only accepts the “href” and some similar props.
- Link Works With Anything inside. Just like an anchor, a button, you can place any React components or even a div
- The only requirement for components placed inside a Link is they should accept an onClick prop
// This is the Link API
import Link from 'next/link'
const Index = () => (
<div>
<Link href="/about">
<a>About Page</a>
</Link>
<p>Hello Next.js</p>
</div>
)
export default Index
Route masking
- The as prop is the URL in the browser bar (logical)
- The href prop is the URL the app is seeing (physical)
Example:
const PostLink = props => (
<li>
<Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}>
<a>{props.title}</a>
</Link>
</li>
)
Processing localization
SSR
SSR with custom-server-and-routing
You can use any Node.js Web server with this API.
For instance, with express
const express = require('express')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app
.prepare()
.then(() => {
const server = express()
// Custom route
server.get('/p/:id', (req, res) => {
const actualPage = '/post'
const queryParams = { title: req.params.id }
app.render(req, res, actualPage, queryParams)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
.catch(ex => {
console.error(ex.stack)
process.exit(1)
})
- Install
npm install --save express
npm install --save-dev cross-env
- Package.json
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "cross-env NODE_ENV=production node server.js"
}
}
SSR vs Client Side
If we can only see the message on the browser console. That's because we navigated to the post page via the client side.
When we click on a link wrapped with the Next.js <Link> component, the page transition takes place in the browser, without making a request to the server.
If you just visit a post page directly by typing the URL in the browser address (eg:- http://localhost:3000/p/975) you will see the console message printed on the server but not in the client.
Query String
dynamic pages using query strings client side
We're injecting the router property (included the query string) into a component by calling withRouter on it
import { withRouter } from 'next/router'
import Layout from '../components/MyLayout.js'
// Query prop of router is injected into props thanks to withRouter
const Content = withRouter(props => (
<div>
<h1>{props.router.query.title}</h1>
<p>This is the blog post content.</p>
</div>
))
const Page = withRouter(props => (
<Layout>
<Content />
</Layout>
))
export default Page
Fetch data
Browser - Fetching Resources (Request/Response) in Next
Next.js comes with an async function called getInitialProps to fetch data for pages.
- It fetch data via a remote data source and pass it as props
- We can write our getInitialProps to work on both server and the client.
By using the getInitialProps static async function, we can fetch data and send them as props to our page.
Example: passing data into our Index page as the shows prop.
# a web api fetch library wrapper that works on the server and client side
npm install --save isomorphic-unfetch
import Layout from '../components/MyLayout.js'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'
const Index = (props) => (
<Layout>
<h1>Batman TV Shows</h1>
<ul>
{props.shows.map(show => (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
)
Index.getInitialProps = async function() {
const res = await fetch('https://api.tvmaze.com/search/shows?q=batman')
const data = await res.json()
// If the message is printed on the server, it's a SSR otherwise it's a client rendering
console.log(`Show data fetched. Count: ${data.length}`)
return {
// passing data into our page as the 'shows' prop.
shows: data.map(entry => entry.show)
}
}
export default Index
Styling
Next has a built-in Css In Js framework called styled-jsx
Style are defined inside the <style jsx></node> element with a Js template string
Styled jsx works as a babel plugin (all necessary prefixing and CSS validation is done inside the plugin). It will parse all of the CSS and apply it in the build process. (The styles get applied without any additional runtime overhead)
It also supports having constraints inside styled-jsx. In the future, you will be able to use any dynamic variable inside styled-jsx. That is why CSS needs to go inside of a template string. ({})
Scope of style is:
- by default, local to a component
- or global by using a global selector
Next.js support also other solution next.js css-in-js
Local
import Layout from '../components/MyLayout.js'
import Link from 'next/link'
function getPosts() {
return [
{id: 'hello-nextjs', title: 'Hello Next.js'},
{id: 'learn-nextjs', title: 'Learn Next.js is awesome'},
{id: 'deploy-nextjs', title: 'Deploy apps with ZEIT'}
]
}
export default function Blog() {
return (
<Layout>
<h1>My Blog</h1>
<ul>
{getPosts().map(post => (
<li key={post.id}>
<Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
<style jsx>{`
h1,
a {
font-family: 'Arial';
}
ul {
padding: 0;
}
li {
list-style: none;
margin: 5px 0;
}
a {
text-decoration: none;
color: blue;
}
a:hover {
opacity: 0.6;
}
`}</style>
</Layout>
)
}
Global
<style jsx global></script>
Example:
npm install --save react-markdown
import Layout from '../components/MyLayout.js'
import { withRouter } from 'next/router'
import Markdown from 'react-markdown'
export default withRouter(props => (
<Layout>
<h1>{props.router.query.title}</h1>
<div className="markdown">
<Markdown
source={`
This is our blog post.
Yes. We can have a [link](/link).
And we can have a title as well.
### This is a title
And here's the content.
`}
/>
</div>
<style jsx global>{`
.markdown {
font-family: 'Arial';
}
.markdown a {
text-decoration: none;
color: blue;
}
.markdown a:hover {
opacity: 0.6;
}
.markdown h3 {
margin: 0;
padding: 0;
text-transform: uppercase;
}
`}</style>
</Layout>
))
HTML page generation
Next.js 3.0 comes with a feature that allows you to export an app into a set of HTML pages. See Export into a Static HTML App
Lazy loading
Code splitting
Next.js does automatic code splitting and it is based on the pages in your app. For example, if one of your modules is used at-least in half of your pages, then it moves into the main JavaScript bundle. If not, that module stays inside the page's bundle.
React Component
Deploy
You can deploy a Next.js app to anywhere you can run Node.js.
Locally
Build produce an optimized set of code for production
"scripts": {
"build": "next build"
"start": "next start"
"startonlinuxport": "next start -p $PORT"
"startonwindowsport": "next start -p %PORT%"
}
npm run build
Creating an optimized production build ...
Compiled successfully.
┌ /
├ /_app
├ /_document
├ /_error
├ /about
└ /post
- Once on the default 300 port
npm run start
- on windows, two instances with two different ports
:: Install cross-env globally
npm install cross-env -g
:: Run on port 8000
cross-env PORT=8000 npm run startonwindowsport
:: Run on port 9000
cross-env PORT=9000 npm run startonwindowsport
now
The now platform has special builder for next application called @now/next. The builder will convert the next pages into a series of individual lambdas and serves them statically. More … see Create a Next.js Application and Deploy with Now guide
module.exports = {
target: 'serverless'
}
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@now/next" }],
"name": "<name-for-your-project>"
}
- Install now-cli
npm install -g now
- run the “now” command from your terminal inside your app's root directory.
now