Next is a react framework for sever rendering applications. These notes are based on beginner's tutorial . Installation Create a Next project with npx create-next-app appNAME Or npx create-next-app . to install into existing folder. package.json "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "export": "next build && next export", "lint": "next lint" }, Run dev server with npm run dev command Compiled a production build by npm run build Start built production version with npm run start With npm run export we compiled a static pages into /one folder, which do not require a node server Routing pages/ folder pages/ folder is needed for routing. Separate UI components into some other folder, for ex. components/ folder. Next goes with file system based routing & doesn't require any 3rd party routing lib When a file with component is added into the pages/ folder, it becomes available as a route Nested folder pattern is supported Default returned component with any function name from index.js file within pages/ folder becomes a domain's root file A component from about.js file will be accessed by /about url If we put about.js into the folder/ , then it will be accessed by folder/about url Rename about.js into index.js and the component becomes a folder root component with folder/ path Dynamic file We can create a dynamic route using parameter name inside square brackets in file's name pages/products/[id].js Parameter can be extracted via useRouter().query.param Real file has a higher specificity, than dynamically routed page // pages/products/[id].js import { useRouter } from "next/router" export default function ProductDetails() { const router = useRouter() const id = router.query.id return <h1>Details about product {id}</h1> } Following component overwrites component with dynamic route // pages/products/1.js export default function index() { return <h2>Specific product 1</h2> } Dynamic folder // pages/products/[folder]/[product].js import { useRouter } from "next/router" export default function ProductDetails() { const router = useRouter() const { folder, product } = router.query return <h1>Details about {product} from {folder}</h1> } Catch all pages routes Can be done via folder with following path structure /folder/[...params] Component will be returned for any nested /folder/A/B/C/... url starting from folder useRouter().query.params contains array of url parameters ["A", "B", "C"] If we go to /folder url, we get 404 page To avoid it we may use additional pair of square brackets /folder/[[...params]] import { useRouter } from "next/router" import randomNumFromTo from "../../helpers/randomNumFromTo" export default function ProductDetails() { const router = useRouter() const {params} = router.query if (params === undefined) return <>not yet</> const min = params[0] const max = params[1] const randomNum = randomNumFromTo(min, max) return <>random number between {min}...{max} is <b>{randomNum}</b></> } Navigation Link Use <Link href='/page1'><a>text</a></Link> for clint side routing without a page refresh If no anchor tag inside the Link tag, then add code passHref attribute <Link href='/page1' passHref><div>text</div></Link> In case of external page use general anchor <a href='google.com'>google</a> tag Attribute replace removes the browser history stack <Link replace href='/page1'><a>text</a></Link> // page1.js import Link from "next/link" const Page1 = () => <> <h2>Page 1</h2> <Link href='/page2'><a>Go to Page 2 via Link tag</a></Link> <a href='/page2'>Go to Page 2 via a tag</a> </> export default page1 Programmatic navigation Use useRouter().push('url') or useRouter().replace('url') to programmatically navigate to a url. // page1.js import { useRouter } from "next/router" export default function Page1() { const router = useRouter() return <> <h2>Page 1</h2> <button onClick={() => router.push('/page2')}>Go to Page 2</button> <button onClick={() => router.replace('/page3')}>Go to Page 3</button> </> } 404 To customize the default 404 page just put 404.js file into pages folder. // pages/404.js export default function FourZeroFour() { return <div>My custom 404</div> } Pre-rendering React generates html page on the clint side in browser Next does it on a server side and ships ready-made html Pre-rendering is good for SEO. Static server generation (SSG) Page is generated in advance at the build time when we compile an application It is done by default by Next It is recommended way, because it can be built it ones, cached & pushed to a CDN SSG is done when we make production build by npm run build It is done automatically every time we make changes in development mode Content is created and goes into .next folder by default SSG with getStaticProps() can fetch data from external source SSG with getStaticPaths() can generate pages dynamically With getStaticPaths() & fallback: true the page is not generated at build time but is generated on a user's initial request With incremental static re-generation getStaticProps() & revalidate: 10 a page can be regenerated after some time Pre-render page static pages With getStaticProps() function we can fetch external data and pass it into a component as props at page production generation time We can of course also fetch data with useEffect() on component load, but data will not be pre-rendered in such scenario Returned object from getStaticProps() function, goes into a component's props in the same file Function should return an object with a props key, like return { props: { a: 1, b: 2}} Function runs at a build time, meaning ones a production version is built by npm run build and every time the page is updated or changed in dev mode I have checked the data fetching with random api and it really generates content based on a data at the production building moment and this data persists If data is returned from an api request, it will be retrieved only ones on project production compilation getStaticProps() function runs on a server, not inside the browser Works only in the pages folder, but not in a regular component console.log data within a function will be displayed in a terminal, but not in a browser It is a node.js code, which has an access to the file system via fs module Sensitive information can be used inside, because code is not exposed by a browser // posts/index.js export default function Index(props) { return ( <> <h3>Posts</h3> {props.data.map(post => ( <div key={post.title}> Posts: <b>{post.title.slice(0, 5)}</b> with id: <b>{post.id}</b> </div> ))} </> ) } export async function getStaticProps() { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const result = await response.json() return { props: { data: result.slice(0, 5), } } } Pre-render dynamic pages We may pre-render dynamic pages, for ex. post articles with following folder structure /posts/[postId].js postId may be any number and we need to tell to Next the possible range, otherwise Next needs to generate endless amount of pages To do that we use a special function getStaticPaths() which returns an array of possible postId parameters // posts/index.js import Link from "next/link" export default function Index(props) { return ( <> <h3>List of posts</h3> {props.data.map(post => ( <div key={post.title}> <Link href={`posts/${post.id}`} passHref> <div> <div>ID: {post.id}</div> <div>Title: {post.title.slice(0, 10)}</div><hr /> </div> </Link> </div> ))} </> ) } export async function getStaticProps() { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const result = await response.json() return { props: { data: result.slice(0, 5) } } } // posts/[postId].js export default function Post(props) { return ( <> <h3>Individual post</h3> <div>Post id: {props.data.id}</div> <div>Title: {props.data.title}</div> <div>Body: {props.data.body}</div> </> ) } export async function getStaticPaths() { return { paths: [ { params: { postId: '1' } }, { params: { postId: '2' } }, { params: { postId: '3' } }, { params: { postId: '4' } }, { params: { postId: '5' } }, ], fallback: false } } export async function getStaticProps(context) { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${context.params.postId}`) const result = await response.json() return { props: { data: result, }, } } When /posts url is hit in production build, .json files are pre-fetched for all 5 posts There is no network activity anymore when we go to an individual post. getStaticPaths() with dynamic return object A getStaticPaths() function's output object can be hardcoded as in the example above or dynamically generated. Here is dynamic version. export async function getStaticPaths() { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const result = await response.json() const paths = result.map(item => ({ params: { postId: `${item.id}`}})) return { paths: paths, fallback: false } } getStaticPaths() & fallback fallback key can have 3 possible values: fallback: false fallback: true fallback: 'blocking' fallback: false The paths returned from getStaticPaths() will be rendered at build time by getStaticProps() Paths not returned by getStaticPaths() will result in a 404 page Suitable for small number of paths to pre-render Suitable when new pages are not added often fallback: true Paths returned from getStaticPaths() will be also generated at build time No 404 page for unavailable paths, but "fallback" page version is rendered on the first request. At the same time, Next.js will generate the requested page running getStaticProps() Then the page will be swapped from the fallback page to the newly generated one Subsequent requests to the same path will serve already generated page, just like other pages pre-rendered at build time. Pages are pre-fetched if a Link comes into the viewport. // posts/[postId].js import { useRouter } from "next/router" export default function Post(props) { const router = useRouter() if (router.isFallback) return <h1>Loading...</h1> return ( <> <h3>Individual post</h3> <div>Post id: {props.data.id}</div> <div>Title: {props.data.title}</div> <div>Body: {props.data.body}</div> </> ) } export async function getStaticPaths() { return { paths: [ { params: { postId: '1' } }, { params: { postId: '2' } }, { params: { postId: '3' } }, ], fallback: true } } export async function getStaticProps(context) { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${context.params.postId}`) const result = await response.json() console.log(result) return { props: { data: result, }, } } In the example above we pre-render 3 pages and when hit another 97 ones, like posts/4 , we get the fallback component 'Loading...', which is swapped ones the post is loaded. Next time we hit this page we just get the post page immediately without a fallback. fallback: 'blocking' Same as fallback: true , but without fallback render while page is fetching A user just waits till the page is loaded SSG issues Production build time can be long for big number of pages Even for one minor change on a page we have to re-build the whole app Page data is frozen until next page production re-build Even if data at end point api is changed we still serve stale data, which was fetched at the production build time Incremental static re-generation can help to solve this problem Incremental static re-generation (ISR) With ISR can statically re-generate individual pages without needing to rebuild the entire site In the getStaticProps() function we can specify a revalidate key The value for revalidate is the number of seconds after which a page re-generation can occur A re-generation is initiated only if a user makes a request after the revalidate time Revalidate does not mean the page automatically re-generates every 10 or something seconds If a re-generation fails then the previously cached HTML will be be served export async function getStaticProps() { const response = await fetch('https://jsonplaceholder.typicode.com/posts') const result = await response.json() console.log(result) return { props: { data: result }, revalidate: 10 } } Http response header is set ot Cache-Control: s-maxage=10, stale-while-revalidate If we hit the page withing 10 seconds we get pre-rendered page The next hit after 10 seconds will trigger a page re-generation But previously pre-rendered page will be served still And only the next page hit will serve an updated data Problems of SSG Data can be stalled Even with re-generation we can not guarantee that a clients get's an up-to-data data Of course we may fetch data on a client side, but then we lost SEO We do not have access to the request and can not generate user specific page Server-side rendering (SSR) Next.js allows you to pre-render a page not at build time but at request time The HTML is generated for every incoming request HTML pages are not stored in pages folder, but served pre-rendered directly to a client The most updated data is served SSR is required when you need to fetch personalized data at a request time with SEO in mind SSR is slower than SSG, use it when necessary Render static pages With getServerSideProps() function we can fetch external data and pass it into a component as props at request time Returned object from getServerSideProps() , goes into a component's props in the same file Function should return an object with a props key, like return { props: { a: 1, b: 2}} Function can be run only in a page and not in a regular component file Function runs at user's request Data fetching from random api generates new page every new request getServerSideProps() function runs on a server, but not inside the browser. It has access to the file system and we can keeps sensitive data there. Code will not be exposed in bundle to a browser // users/index.js import Link from "next/link" export default function Index(props) { return ( <> <h3>List of users</h3> {props.data.map(user => ( <Link key={user.first_name} href={`users/${user.first_name}`} passHref> <div>Name: {user.first_name}</div> </Link> ))} </> ) } export async function getServerSideProps() { const response = await fetch('https://random-data-api.com/api/users/random_user?size=5') const result = await response.json() console.log(result) return { props: { data: result }, } } Render dynamic pages // users/[userId].js import { useRouter } from "next/router" export default function User(props) { return ( <> <h3>User details</h3> <div>First name: {props.data.first_name}</div> <div>Last name: {props.data.last_name}</div> <div>Phone: {props.data.phone_number}</div> </> ) } export async function getServerSideProps(context) { const response = await fetch(`https://random-data-api.com/api/users/random_user?id=${context.params.userId}`) const result = await response.json() return { props: { data: result, }, } } Parameters of getServerSideProps(context) With SSR we have an access to request & response parameters, unlike with SSG. Documentation for context parameters of getServerSideProps(context) function. For example we can modify request and read response parameters. Check how we set and read cookies and read url parameters. export async function getServerSideProps(context) { const response = await fetch(`https://random-data-api.com/api/users/random_user?id=${context.params.userId}`) // set cookie context.res.setHeader('Set-Cookie', ['message=hello']) // read cookie console.log(context.req.headers.cookie) // message=hello //read url parameters for http://localhost:3000/users/2488?a=1&b=2 console.log(context.query) // { a: '1', b: '2', userId: '2488' } const result = await response.json() return { props: { data: result, }, } } Client side data fetching useEffect() hook Some data is not required for pre-rendering and SEO, number of likes for a post for ex. It can be done by basic react approach by fetching data inside useEffect() hook. Pre-rendered page will not contain fetched data from useEffect() Here is the example how to do that. Version with fetching via useEffect() // pages/user.js import { useState, useEffect } from 'react' export default function User() { const [isLoading, setIsLoading] = useState(true) const [userState, setUserState] = useState(null) useEffect(() => { async function fetchUser() { const response = await fetch('https://random-data-api.com/api/users/random_user') const data = await response.json() setUserState(data) setIsLoading(false) } fetchUser() }, []) if (isLoading) return <h2>Loading...</h2> return <h2>Name: {userState.first_name}</h2> } Pre-rendering + client side data fetching Initially 5 names are rendered by /list-of-users path With input & button we change the number of names to render When we change number of names to render we programmatically append /list-of-users?size=10 query to the path We can share /list-of-users?size=10 and the link will pre-render 10 names // pages/list-of-users.js import { useRouter } from "next/router" import { useState } from "react" export default function Index({ users, usersNum }) { const [usersState, setUsersState] = useState(users) const [usersNumState, setUsersNumState] = useState(usersNum) const updateInput = (e) => setUsersNumState(e.target.value) const router = useRouter() async function fetchUsers() { const response = await fetch(`https://random-data-api.com/api/users/random_user?size=${usersNumState}`) const result = await response.json() setUsersState(result) router.push(`list-of-users?size=${usersNumState}`, undefined, { shallow: true }) } return ( <> <input type="number" value={usersNumState} onChange={updateInput}/> <button onClick={fetchUsers}>Show</button> <h3>List of users</h3> {usersState.map(user => <div key={user.first_name}>Name: {user.first_name}</div> )} </> ) } export async function getServerSideProps(context) { const usersNum = context.query.size || 5 const response = await fetch(`https://random-data-api.com/api/users/random_user?size=${usersNum}`) const result = await response.json() return { props: { users: result, usersNum: usersNum }, } } Initial pre-render of 5 names Change number of names with button and share the link will result in 10 names pre-render SWR - react hooks for data fetching SWR - stale while revalidate strategy SWR is a strategy to first return the data from cache (stale), then send the fetch request (revalidate), and finally come with the up-to-date data It's easier to use SWR, than make own fetching with useEffect() & useState() hooks When the user's laptop wakes up or switch between tabs, the data will be refreshed automatically Install SWR by npm i swr Version with SWR, look how cleaner it is. // pages/user-swr.js import useSWR from 'swr' const fetcher = async () => { const response = await fetch('https://random-data-api.com/api/users/random_user') const data = await response.json() return data } export default function UserSWR() { const { data, error } = useSWR('uniqueReqKey', fetcher) if (error) return 'An error has occurred.' if (!data) return 'Loading...' return <h2>Name: {data.first_name}</h2> } API routes We can write RESTful endpoints APIs that can be called by the front-end API routes use folder structure Within the pages/ folder place the api/ folder Next gives a possibility to write full-stack React + Node applications Code inside api/ folder is never bundled into front-end code API routes structure // pages/api/index.js export default function handler(req, res) { res.status(200).json({ name: 'api home route'}) } // pages/api/dashboard.js export default function handler(req, res) { res.status(200).json({ name: 'api dashboard route'}) } // pages/api/blog/index.js export default function handler(req, res) { res.status(200).json({ name: 'api blog route'}) } // pages/api/blog/summary.js export default function handler(req, res) { res.status(200).json({ name: 'api blog summary route'}) } GET request Data file // /data/comments.js export const comments = [ { id: 1, text: 'This is the first comment' }, { id: 2, text: 'This is the second comment' }, { id: 3, text: 'This is the third comment' } ] GET api file // /pages/api/comments/index.js import { comments } from "../../../data/comments"; export default function handler(req, res) { res.status(200).json(comments) } UI component // pages/comments/index.js import { useState } from "react" export default function Component() { const [comments, setComments] = useState([]) const getComments = async () => { const response = await fetch('api/comments') const result = await response.json() setComments(result) } return ( <> <button onClick={getComments}>Get comments</button> { comments.map(comment => <div key={comment.id}>{comment.text}<hr/></div>) } </> ) } Result POST request Same example as above, but with input field + submit button to add a new comment. GET + POST api file // /pages/api/comments/index.js import { comments } from "../../../data/comments"; export default function handler(req, res) { if (req.method === 'GET') { res.status(200).json(comments) } if (req.method === 'POST') { const comment = req.body.comment const newComment = { id: Date.now(), text: comment } comments.push(newComment) res.status(201).json(newComment) } } UI component // pages/comments/index.js import { useState } from "react" export default function Component() { const [comments, setComments] = useState([]) const [comment, setComment] = useState('') const updateInputVal = (e) => setComment(e.target.value) const getComments = async () => { const response = await fetch('api/comments') const result = await response.json() setComments(result) } const submitComment = async () => { const response = await fetch('api/comments', { method: 'POST', body: JSON.stringify({comment: comment}), headers: { 'Content-Type': 'Application/json' } }) const data = await response.json() } return ( <> <input type="text" value={comment} onChange={updateInputVal} /> <button onClick={submitComment}>Submit comment</button> <button onClick={getComments}>Get comments</button> { comments.map(comment => <div key={comment.id}>{comment.text}<hr/></div>) } </> ) } Dynamic route Needed for DELETE request to specify a comment we want to delete // /pages/api/comments/[commentId].js import { comments } from "../../../data/comments" export default function handler(req, res) { if (req.method === 'GET') { const commentId = req.query.commentId const comment = comments.find(comment => comment.id === parseInt(commentId)) res.status(200).json(comment) return } } DELETE request Add delete button into UI // pages/comments/index.js import { useState } from "react" export default function Component() { const [comments, setComments] = useState([]) const [comment, setComment] = useState('') const updateInputVal = (e) => setComment(e.target.value) const getComments = async () => { const response = await fetch('api/comments') const result = await response.json() setComments(result) } const submitComment = async () => { const response = await fetch('api/comments', { method: 'POST', body: JSON.stringify({comment: comment}), headers: { 'Content-Type': 'Application/json' } }) const data = await response.json() } const deleteComment = async (commentId) => { const response = await fetch(`/api/comments/${commentId}`, { method: 'DELETE' }) const data = await response.json() getComments() } return ( <> <input type="text" value={comment} onChange={updateInputVal} /> <button onClick={submitComment}>Submit comment</button> <button onClick={getComments}>Get comments</button> { comments.map(comment => ( <div key={comment.id}> {comment.text} <button onClick={() => deleteComment(comment.id)}>Delete</button> <hr/> </div> )) } </> ) } Handle DELETE request in dynamic route api // /pages/api/comments/[commentId].js import { comments } from "../../../data/comments" export default function handler(req, res) { const commentId = req.query.commentId if (req.method === 'GET') { const comment = comments.find(comment => comment.id === parseInt(commentId)) res.status(200).json(comment) return } if (req.method === 'DELETE') { const deletedComment = comments.find(comment => comment.id === parseInt(commentId)) const index = comments.findIndex(comment => comment.id === parseInt(commentId)) comments.splice(index, 1) res.status(200).json(deletedComment) return } } PUT, PATCH request Has same logic as DELETE request Catch all API routes It is similar to catching all pages routes via [...params] or [[...params]] to include also a root route // /pages/api/[...params].js export default function handler(req, res) { const params = req.query.params res.status(200).json(params) } Styles Global styles Global styles are kept in styles/globals.css And imported into pages/_app.js Styles will be applicable for all pages For ex. we can add bootstrap with npm i bootstrap And include it within pages/_app.js with import 'bootstrap/dist/css/bootstrap.min.css' Component scoped styles Scoped styles can be stored in FileName.module.css Import styles into a file with import styles from '...path/FileName.module.css' To apply styles we need to access it via styles object CSS modules can only target elements using classnames or ids and not using tag names Such styles will get unique class names No need to worry about styles collisions CSS module /* users/styles.module.css */ .heading h3 { color: red; } Component // users/index.js import Link from "next/link" import styles from './styles.module.css' export default function Index(props) { return ( <div className={styles.heading}> <h3>List of users</h3> {props.data.map(user => ( <Link key={user.first_name} href={`users/${user.id}`} passHref> <div>Name: {user.first_name}</div> </Link> ))} </div> ) } export async function getServerSideProps() { const response = await fetch('https://random-data-api.com/api/users/random_user?size=5') const result = await response.json() console.log(result) return { props: { data: result }, } } SASS / SCSS Add SASS with npm i sass CSS module for the same component as above /* users/styles.module.scss */ .heading { background: lightblue; h3 { color: red; } [href] { color: yellow; } } Inline styles Inline styles can be used in JSX <div style={{fontSize: "50px"}}>div</div> styled-jsx Detailed info reat at https://nextjs.org/blog/styling-next-with-styled-jsx export default function HelloWorld() { return ( <div> Hello world <p>scoped!</p> <style jsx>{` p { color: blue; } div { background: red; } @media (max-width: 600px) { div { background: blue; } } `}</style> <style global jsx>{` body { background: black; } `}</style> </div> ) } SASS in styled-jsx npm install --save-dev @styled-jsx/plugin-sass sass Create a .babelrc.json file in the project folder Restart the next server and reload the page. { "presets": [ [ "next/babel", { "styled-jsx": { "plugins": ["@styled-jsx/plugin-sass"] } } ] ] } Styled components Add styled components npm i styled-components Install babel plugin npm i -D babel-plugin-styled-components Then create a .babelrc file in the root of the project Create /pages/_document.js Add following code to inject the server side rendered styles into the <head> .babelrc { "presets": [ "next/babel" ], "plugins": [ [ "styled-components", { "ssr": true, "displayName": true, "preprocess": false } ] ] } _document.js // /pages/_document.js import Document from 'next/document' import { ServerStyleSheet } from 'styled-components' export default class MyDocument extends Document { static async getInitialProps(ctx) { const sheet = new ServerStyleSheet() const originalRenderPage = ctx.renderPage try { ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />), }) const initialProps = await Document.getInitialProps(ctx) return { ...initialProps, styles: ( <> {initialProps.styles} {sheet.getStyleElement()} </> ), } } finally { sheet.seal() } } } UI // pages/styled-cmpt.js import styled from 'styled-components' export default function Index() { return ( <Div> <h3>h3</h3> <div>div</div> </Div> ) } const Div = styled.div` border: 5px solid green; padding: 5px; h3 { color: orange; } div { color: blue; } ` Layout It is the website structure, where we have on all pages fixed navbar and footer for ex. Layout can be defined in _app.js file Common header & footer // components/layout/Header.js export default function Header() { return <div className='my-header'>Header</div> } // components/layout/Footer.js export default function Footer() { return <div className='my-footer'>Footer</div> } /* styles/layout.css */ .my-header { background-color: #264653; color: #f4a261; font-size: 30px; text-align: center; padding: 30px; } .my-footer { background-color: #264653; color: #e9c46a; font-size: 30px; text-align: center; padding: 20px; } .content { display: flex; justify-content: center; align-items: center; min-height: 100vh; } // pages/_app.js import Footer from '../components/layout/Footer' import Header from '../components/layout/Header' import '../styles/globals.css' import '../styles/layout.css' export default function MyApp({ Component, pageProps }) { return ( <> <Header /> <Component {...pageProps} /> <Footer /> </> ) } Exception from layout For example we do not need the header on about page We can add specific property getLayout into a component function // pages/about.js import Footer from "../components/layout/Footer" export default function About(params) { return <div className="content">About text</div> } About.getLayout = (page) => ( <> {page} <Footer /> </> ) In the _app.js we can check the availability of Component.getLayout property and specifically render such components // pages/_app.js import Footer from '../components/layout/Footer' import Header from '../components/layout/Header' import '../styles/globals.css' import '../styles/layout.css' export default function MyApp({ Component, pageProps }) { if (Component.getLayout) { return Component.getLayout(<Component {...pageProps} />) } return ( <> <Header /> <Component {...pageProps} /> <Footer /> </> ) } Head component <head></head> tag is very minimalistic by default in Next.js Data in head tag is very important for SEO Next.js provide a special <Head/> component Every page component can have its own <Head/> and more specific will be merge and replace tag from the parent page // pages/_app.js import Head from 'next/head' import Footer from '../components/layout/Footer' import Header from '../components/layout/Header' import '../styles/globals.css' import '../styles/layout.css' export default function MyApp({ Component, pageProps }) { if (Component.getLayout) { return Component.getLayout(<Component {...pageProps} />) } return ( <> <Head> <title>Blog page</title> <meta name='description' content='Nice blog channel' /> </Head> <Header /> <Component {...pageProps} /> <Footer /> </> ) } We can pass dynamic data into the <Head/> component. // /pages/blog/[blogId].js import Head from 'next/head' export default function Blog({ title, description }) { return ( <> <Head> <title>{title}</title> <meta name='description' content={description} /> </Head> <h1> Some blog text </h1> </> ) } export async function getServerSideProps() { return { props: { title: 'Article Title', desc: 'Article description' } } } Image component Images should be placed under public/ folder Path to a file file.png inside public/ is /file.png General image When <img> is used, files are downloaded at its original big size and increases the load time. // pages/imgs.js export default function Imgs() { return ( <> {['1', '2', '3', '4', '5'].map(path => { return ( <div key={path}> <img src={`/${path}.jpeg`} alt='some desc' width='400' height='auto' /> </div> ) })} </> ) } Image component <Image> component from the Next.js library optimizes an image size Images are lazy loaded Blurred image can be pre-loaded as a placeholder, which eliminates any shifts when an actual image is downloaded and takes a place over // pages/imgs.js import Image from 'next/image' export default function Imgs() { return ( <> {['1', '2', '3', '4', '5'].map(path => { return ( <div key={path}> <Image src={`/${path}.jpeg`} alt="some desc" width="300px" height="100px" // layout="fill" objectFit="contain" placeholder="blur" blurDataURL={`/${path}.jpeg`} /> </div> ) })} </> ) } Path alias Add jsconfig.json into the root folder { "compilerOptions": { "baseUrl": ".", "paths": { "/*": ["./*"], "@st/*": ["/styles/*"] } } } With "baseUrl": "." we can use / to access the root With "/*": ["./*"] we can avoid using / to access the root With "@st/*": ["/styles/*"] we can use @st/ for /styles/ // pages/_app.js import Footer from '/components/layout/Footer' import Header from 'components/layout/Header' import '/styles/globals.css' import '@st/layout.css' export default function MyApp({ Component, pageProps }) { return ( <> <Header /> <Component {...pageProps} /> <Footer /> </> ) } TypeScript support Create tsconfig.json file in the root Add TS package npm i -D typescript @types/react @types/node Restart server by npm run dev tsconfig.json file will be populated automatically Also next-env.d.ts file is created for some type (no clue) jsconfig.json is not in use anymore and we need to copy its content into tsconfig.json Restart again npm run dev Some specific types import { GetStaticProps, GetStaticPaths, GetServerSideProps, NextApiRequest, NextApiResponse } from 'next' // props export const getStaticProps: GetStaticProps = async (context) => { // ... } export const getStaticPaths: GetStaticPaths = async () => { // ... } export const getServerSideProps: GetServerSideProps = async (context) => { // ... } // api export default (req: NextApiRequest, res: NextApiResponse) => { res.status(200).json({ name: 'John Doe' }) } // or type Data = { name: string } export default (req: NextApiRequest, res: NextApiResponse<Data>) => { res.status(200).json({ name: 'John Doe' }) } Redirects Redirects are useful to wire requests to other page during your page maintenance. We need to add re-direct instructions into next.config.js file and restart the server. const nextConfig = { reactStrictMode: true, redirects: async () => { return [ { source: '/about', destination: '/', permanent: false }, { source: '/old-blog/:id', destination: '/new-blog/:id', permanent: true } ] } } module.exports = nextConfig Now when we hit the /about url we are re-directed to the root folder. Environment variable Environment variables can keep some data, which are not shipped to the browser Variables should be placed in .env.local file at the root File is git ignored automatically Values can be accessed from process.env object By default environment variables can not be exposed to the front-end If we want it to be available in the browser, then variable to be pre-fixed with NEXT_PUBLIC_ # /.env.local DB_USER=Anton DB_PASSWORD=123 NEXT_PUBLIC_NOT_A_SECRET=I am not a secret export default function Home(props) { return ( <> <h1> Home page </h1> <div>User: <b>{props.user}</b></div> <div>Password: <b>{props.password}</b></div> <div>Direct access to an environment variable: <b>{process.env.NEXT_PUBLIC_NOT_A_SECRET}</b></div> </> ) } export async function getServerSideProps() { const user = process.env.DB_USER const password = process.env.DB_PASSWORD return { props: { user, password }, } } Authentication Authentication - identity of a user Authorization - verifies what access permissions the user has NextAuth.js NextAuth.js is the open source solution for authentication Install the package npm i next-auth GitHub authentication Create auth keys on GitHub profile Put them into environment variables. # /.env.local GITHUB_ID=f86f8a443fbfdc1194e2 GITHUB_SECRET=8df58a01884796ac82afb7e2d64ec712fde6bdb2 Add dynamic api route // pages/api/auth/[...nextauth].js import NextAuth from 'next-auth/next' import GitHubProvider from 'next-auth/providers/github' export default NextAuth({ providers: [ GitHubProvider({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }) ] }) Hit http://localhost:3000/api/auth/signin url and sign in button will be returned by the next-auth lib When we sign in the JWT token will be stored in cookies When hit http://localhost:3000/api/auth/signout url the token will be removed. Sign in & out with buttons Of course we can not expect users to sign by hitting an url, so we need to make a button or a link Just include a link with href and it will work Or we can include signIn() & signOut() function from the package If we have just one login provider, we may add it as an argument and will not have any page redirection // components/Navbar.js import Link from 'next/link' import { signIn, signOut } from "next-auth/react" export default function Navbar() { function signInFunc(e) { e.preventDefault() signIn('github') } function signOutHandler(e) { e.preventDefault() signOut() } return ( <nav className='header'> <h1 className='logo'><a href='#'>NextAuth</a></h1> <ul className={`main-nav`}> <li><Link href='/'><a>Home</a></Link></li> <li><Link href='/dashboard'><a>Dashboard</a></Link></li> <li><Link href='/blog'><a>Blog</a></Link></li> <li><Link href='/api/auth/signin'><a onClick={signInHandler}>Sign In</a></Link></li> <li><Link href='/api/auth/signout'><a onClick={signOutHandler}>Sign Out</a></Link></li> </ul> </nav> ) } Client-side authentication We need to hide "sign in" button and show "sign out" if we are signed in We can use useSession() hook from the package The hook returns an object with data object and a status flag const { data, status } = useSession() As far as we do our authentication check on a client side, we have a flicker between "sign in" & "sign out" buttons due to request time. To avoid it we can apply some transition. // components/Navbar.js import Link from 'next/link' import { signIn, signOut, useSession } from "next-auth/react" export default function Navbar() { const {data, status} = useSession() function signInFunc(e) { e.preventDefault() signIn('github') } function signOutFunc(e) { e.preventDefault() signOut() } return ( <nav className='header'> <h1 className='logo'><a href='#'>NextAuth</a></h1> <ul className={`main-nav ${status === 'loading' ? 'loading' : 'loaded'}`}> <li><Link href='/'><a>Home</a></Link></li> <li><Link href='/dashboard'><a>Dashboard</a></Link></li> <li><Link href='/blog'><a>Blog</a></Link></li> {!data && status === "unauthenticated" && ( <li><Link href='/api/auth/signin'><a onClick={signInFunc}>Sign In</a></Link></li> )} {data && status === 'authenticated' && ( <li><Link href='/api/auth/signout'><a onClick={signOutFunc}>Sign Out</a></Link></li> )} </ul> </nav> ) } // pages/_app.js import Navbar from '../components/Navbar' import { SessionProvider } from "next-auth/react"; import '/styles/globals.css' import '@st/layout.css' import '../components/Navbar.css' export default function MyApp({ Component, pageProps: {session, ...pageProps }}) { return ( <> <SessionProvider session={session}> <Navbar /> <Component {...pageProps} /> </SessionProvider> </> ) } .loading { opacity: 0; transition: all 0.2s ease-in; } .loaded { opacity: 1; transition: all 0.2s ease-in; } Secure pages client side Let view a page only for a logged in user If not logged in, then re-direct to a log in page We can use getSession function from the package The function returns an promise with a session object or null if no session exists & user is not logged in // pages/dashboard.js import { useState, useEffect } from 'react' import { getSession, signIn } from "next-auth/react" export default function Dashboard() { const [loading, setLoading] = useState(true) useEffect(() => { async function securePage() { const session = await getSession() if (session) setLoading(false) if (!session) signIn() } securePage() }, []) if (loading) return <h2>Loading...</h2> if (!loading) return <h1>Dashboard page</h1> } Server-side authentication and page securing We do same logic, but from getServerSideProps() function From any page we return session object into props Props are available at the top component and this session objects falls into <SessionProvider session={pageProps.session}> Now session object is available in every component from useSession() hook // pages/blog.js import { getSession, useSession } from 'next-auth/react' export default function Blog({ msg }) { const {data, status} = useSession() console.log(data, status) // just to show that session data is available return <h1>Blog page - {msg}</h1> } export async function getServerSideProps(context) { const session = await getSession(context) if (!session) { return { redirect: { destination: '/api/auth/signin?callbackUrl=http://localhost:3000/blog', permanent: false } } } return { props: { msg: session ? 'List of paid blogs' : 'List of free blogs', session } } } // pages/_app.js import Navbar from '../components/Navbar' import { SessionProvider } from "next-auth/react"; import '/styles/globals.css' import '@st/layout.css' import '../components/Navbar.css' export default function MyApp({ Component, pageProps}) { return ( <> <SessionProvider session={pageProps.session}> <Navbar /> <Component {...pageProps} /> </SessionProvider> </> ) } Secure API route // pages/api/test-session.js import { getSession } from 'next-auth/react' export default async function apiRoute(req, res) { const session = await getSession({ req }) if (!session) res.status(401).json({ error: 'Unauthenticated user' }) if (session) res.status(200).json({ message: 'Success', session }) } Connecting to a MongoDB Configure mongodb adapter Install mongodb package npm i next-auth @next-auth/mongodb-adapter mongodb Create free database mongodb.com with own credentials and make it accessed from everywhere in network configuration Add db configuration into .env.local file Add database URL into auth api in pages/api/auth/[...nextauth].js file # /.env.local DB_USER=sherbsherb DB_PASSWORD=dummypassword MONGODB_URI=mongodb+srv://$DB_USER:$DB_PASSWORD@quotationtool.75fjj.mongodb.net/myFirstDatabase?retryWrites=true&w=majority // lib/mongodb.js // This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb import { MongoClient } from "mongodb" const uri = process.env.MONGODB_URI const options = { useUnifiedTopology: true, useNewUrlParser: true, } let client let clientPromise if (!process.env.MONGODB_URI) { throw new Error("Please add your Mongo URI to .env.local") } if (process.env.NODE_ENV === "development") { // In development mode, use a global variable so that the value // is preserved across module reloads caused by HMR (Hot Module Replacement). if (!global._mongoClientPromise) { client = new MongoClient(uri, options) global._mongoClientPromise = client.connect() } clientPromise = global._mongoClientPromise } else { // In production mode, it's best to not use a global variable. client = new MongoClient(uri, options) clientPromise = client.connect() } // Export a module-scoped MongoClient promise. By doing this in a // separate module, the client can be shared across functions. export default clientPromise // pages/api/auth/[...nextauth].js import NextAuth from 'next-auth/next' import GitHubProvider from 'next-auth/providers/github' import { MongoDBAdapter } from "@next-auth/mongodb-adapter" import clientPromise from "lib/mongodb" export default NextAuth({ providers: [ GitHubProvider({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }) ], adapter: MongoDBAdapter(clientPromise), }) When we log in the database is populated by next-auth Deployment https://nextjs.org/docs/deployment https://vercel.com/ Post from Medium about manual deployment on self hosted server