Categories
Next.js

Next.js — Dynamic Imports and Static Pages

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at dynamic imports and render static pages with Next.js.

Dynamic Import

ES2020 supports dynamic imports natively.

We can use this within our Next.js to dynamically import our components.

It also works with server-side rendering.

This lets us split our code into manageable chunks.

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}

export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/name'))

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

We imported the dynamic function and passed in a callback that calls import to import the component.

Then we can use the NameComponent in our code.

Now we should see:

hello world

displayed.

Dynamic Component With Named Exports

We can create dynamic components with named exports.

To do that, we can write:

components/name.js

export function Name({ }) {
  return (
    <span>world</span>
  )
}

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name').then((mod) => mod.Name)
)

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

In the callback, we called import with the then callback to return the Name component from the module.

With Custom Loading Component

We can also add a component to show the component when the component loads.

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}
export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name'), { loading: () => <p>loading</p> })

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

We have an object with the loading property in the code with a component as the value.

Disable Server-Side Rendering

Server-side rendering can be disabled by passing in an object into the dynamic function with the ssr property set to false .

For example, we can write:

components/name.js

function Name() {
  return (
    <span>world</span>
  )
}
export default Name

pages/hello.js

import dynamic from 'next/dynamic'

const NameComponent = dynamic(() => import('../components/Name'), { ssr: false }
)

function Hello() {
  return (
    <div>hello <NameComponent /></div>
  )
}

export default Hello

Static HTML Export

We can export our app to static HTML with the next export command.

It works by prerendering all the pages to HTML.

The pages can export a getStaticPaths property that returns all the paths that we want to prerender.

next export should be used when there are no dynamic data changes in our app.

To use it, we run:

next build && next export

next build builds the app and next export gets us the HTML pages.

We can also add both commands to our package.json file:

"scripts": {
  "build": "next build && next export"
}

and run npm run build to run it.

getInitialProps can’t be used with getStaticProps or getStaticPaths on any page.

Conclusion

We can dynamically import components with Next.js.

Also, we can build our Next.js app to static HTML files.

Categories
Next.js

Next.js — Middlewares and Preview Mode

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at route middlewares and preview mode with Next.js.

Connect/Express Middleware Support

We can use Connect compatible middleware.

For example, we can add the cors middleware by installing it and adding to our Next.js app.

To install it, we run:

npm i cors

or

yarn add cors

Then we can add it to our API route by writing:

import Cors from 'cors'

const cors = Cors({
  methods: ['GET', 'HEAD'],
})

function runMiddleware(req, res, fn) {
  return new Promise((resolve, reject) => {
    fn(req, res, (result) => {
      if (result instanceof Error) {
        return reject(result)
      }
      return resolve(result)
    })
  })
}

async function handler(req, res) {
  await runMiddleware(req, res, cors)
  res.json({ message: 'hello world' })
}

export default handler

We imported the Cors function.

Then we use it to create the cors middleware.

We can specify the request methods that can be used with the route.

The runMiddleware function runs the middleware by calling the fn middleware function and then resolve to the result if it’s successful.

Otherwise, the promise is rejected.

The handler is the route handler that runs the middleware and then returns the response.

Response Helpers

Next.js comes with various response helpers.

The res.status(code) method lets us set the status code of the response.

code is an HTTP status code.

res.json(json) method lets us send a JSON response.

json is a valid JSON object.

res.send(body) lets us send an HTTP response, where body is a string, object, or buffer.

res.redirect takes an optional status and path to let us redirects to a specific path or URL.

The default value of status is 307.

For example, we can call them by writing:

export default (req, res) => {
  res.status(200).json({ name: 'hello' })
}

They can be chained together.

Preview Mode

We can preview routes that are statically generated.

Next.js has a preview mode feature to solve the problem.

It’ll render the pages at request time instead of build time and fetch the draft content instead of the published content.

To use it, we call the res.setPreviewData method to do the preview.

For example, we can write:

export default (req, res) => {
  res.setPreviewData({})
  res.end('Preview mode')
}

to enable it.

Securely Accessing Data

We can access data by writing from the preview route and send to our page.

To do this, we create our preview API route by creating the pages/api/preview.js file:

export default async (req, res) => {
  const response = await fetch(`https://yesno.wtf/api`)
  const data = await response.json();
  res.setPreviewData(data)
  res.redirect('/post')
}

We get the data from an API and call setPreviewData so that we can get it from the getStaticProps function in our page JavaScript file.

Then we redirect to our file so that we can see the data on our page.

In pages/post.js , we write:

function Post({ data }) {
  return (
    <div>{data && data.answer}</div>
  )
}

export async function getStaticProps(context) {
  if (context.preview) {
    return {
      props: {
        data: context.previewData
      }
    }
  }
  return { props: { data: { answer: 'not preview' } } }
}

export default Post

We check is preview mode is on by checking the context.preview property.

If it’s true , then we get the preview data from context.previewData .

And then we display the data in our Post component.

Conclusion

We can add preview mode with Next.js so that we can view the output before it goes live.

Also, we can use Connect or Express middleware in our Next.js apps.

Categories
Next.js

Next.js — Dynamic API Routes and Middlewares

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at dynamic API routing and middleware with Next.js.

Dynamic API Routes

We can create dynamic API routes by following the usual file naming convention.

In the pages/api folder, we can create a file with a name that’s surrounded with brackets.

For example, we can create a pages/api/post/[pid].js and write:

export default (req, res) => {
  const {
    query: { pid },
  } = req
  res.end(`Post: ${pid}`)
}

Then we can make a request by going to http://localhost:3000/api/post/foo.

And we’ll get Post: foo displayed as a result.

We can set up our routes with common REST patterns.

For example, we can use GET api/posts/ to get a list of posts.

And GET api/posts/1 gets a single post.

Catch-All API Routes

We can create a catch all API route by using ... in the file name.

For example, we can create the pages/api/post/[...slugs].js file and write:

export default (req, res) => {
  const {
    query: { slugs },
  } = req
  res.end(`Post: ${slugs.join(', ')}`)
}

Then when we go to http://localhost:3000/api/post/foo/bar, we get:

Post: foo, bar

returned.

Optional Catch-all API Routes

Also, we can create catch-all API routes that don’t always expect URL parameters to be passed in.

To do that, we wrap our file name with 2 square brackets around its name.

So we can create a file called pages/api/post/[[...slugs]].js and add:

export default (req, res) => {
  const {
    query: { slugs },
  } = req
  res.end(`Post: ${Array.isArray(slugs) && slugs.join(', ')}`)
}

We have a file with its name wrapped with a square brackets.

In the code, we check if slugs is an array and then return the array entries joined together.

API Middlewares

API roiutes provide built in middleware to parse incoming requests.

They include:

  • req.cookies to parse cookies
  • req.query to parse query strings
  • req.body to parse request bodies.

Custom Config

Every API route can export a config object to change default configs.

For example, we can write:

export default (req, res) => {
  res.end(`Post: ${req.body}`)
}

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '1mb',
    },
  },
}

to create an API route with a size limit imposed on the request body.

Also, we can disable body parsing with:

export default (req, res) => {
  res.end(`Post: ${req.body}`)
}

export const config = {
  api: {
    bodyParser: false,
  },
}

We set bodyParser to false to disable the body parser.

The externalResolver property is a flag that tells the server that the route is being handled by an external resolver.

It’ll disable warnings for unresolved requests.

We can use it by writing:

export default (req, res) => {
  res.end(`Post: ${req.body}`)
}

export const config = {
  api: {
    externalResolver: true,
  },
}

Conclusion

We can add dynamic routes and route middleware with Next.js.

Categories
Next.js

Next.js — Styling Components

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at how to style pages with Next.js.

Built-In CSS Support

Next.js lets us import CSS from JavaScript files.

It extends import beyond the basic JavaScript usage to let us import CSS files.

We can add a global stylesheet to our project.

To do this, we just create a CSS file and import it to pages/_app.js .

So we can write:

body {
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}

in styles.css .

Then in pages/_app.js , we write:

import '../styles/globals.css'
import './styles.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

We added the styles.css import.

The rest of the code is generated when we created the project.

If we want to import from the node_modules folder, then we must do so inside pages/_app.js .

Adding Component-Level CSS

We can add component-level CSS in addition to global CSS.

To do this, we follow the naming convention of [name].module.css , where [name] is the component’s file name.

For example, given that we have pages/yesno.js :

function YesNo({ data }) {
  return <p>{data.answer}</p>
}

export async function getServerSideProps() {
  const res = await fetch(`https://yesno.wtf/api`)
  const data = await res.json()
  return { props: { data } }
}

export default YesNo

We can create a file called yesno.module.js with:

.yesno {
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}

and then we can import it into yesno.js :

import styles from './yesno.module.css';

function YesNo({ data }) {
  return <p className={styles.yesno}>{data.answer}</p>
}

export async function getServerSideProps() {
  const res = await fetch(`https://yesno.wtf/api`)
  const data = await res.json()
  return { props: { data } }
}

export default YesNo

The yesno class in CSS is converted into the yesno property when we import it.

We use it to populate the className prop to set the class names of the elements.

CSS modules are an optional features and it’s only enabled for files with the .module.css extension.

This means we need the .module part of the file name if we want to import them as we did.

We can also use link tags as always to add our CSS files.

CSS modules are automatically concatenated and minified and code-split.

Sass Support

Next.js lets us import SASS files with the .scss and .sass extensions.

We can also use them as CSS modiles with the .module.scss and .module.sass extensions.

To use SASS in our Next.js app, we’ve install the sass package by running:

npm install sass

The SCSS syntax is an extension of the CSS syntax, SASS has an indentation syntax that’s different from CSS.

Therefore, it’s easier to work with SCSS files.

Customizing Sass Options

We can change the paths of the style files to include within the next.config.js file.

For example, we can write:

const path = require('path')

module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
}

to change the path of the style files we want to include.

Conclusion

We can style our Next.js app with CSS and SASS / SCSS.

Categories
Next.js

Next.js — Route Links and Dynamic Routes

We can create server-side rendered React apps and static sites easily Next.js.

In this article, we’ll take a look at routing with Next.js.

Dynamic Route Links

We can add links for dynamic routes.

To do this, we write:

pages/foo.js

import Links from './links';

function Foo() {
  return <div>
    <Links />
    <p>foo</p>
  </div>
}

export default Foo

pages/blog/[slug].js

import { useRouter } from 'next/router'
import Links from '../links';

const Post = () => {
  const router = useRouter()
  const { slug } = router.query

  return <div>
    <Links />
    <p>Post: {slug}</p>
  </div>
}

export default Post

pages/links.js

import Link from 'next/link'

const posts = [
  { slug: 'hello-world', title: 'hello world', id: 1 },
  { slug: 'good-news', title: 'good news', id: 2 },
  { slug: 'great-news', title: 'great news', id: 3 },
]

function Links() {
  return (
    <ul>
      <li>
        <Link href="/foo">
          <a>foo</a>
        </Link>
      </li>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
            <a>{post.title}</a>
          </Link>
        </li>
      ))}
    </ul>
  )
}

export default Links

We have the [slug].js file which displays the slug that we pass in.

router.query has the URL parameters in an object.

It can have as many URL parameters as we pass in.

The useRouter hook has the route data.

We need the brackets to indicate that the file is used for dynamic routes that can accept URL parameters.

The links.js file has the links.

We use the Link component for adding the links.

The href has the URL pattern for the route.

And as has the actual path for the route to fo to.

Also, we have a static link to the foo page.

Therefore, when we click the links, we’ll go to the pages that are mapped to the routes.

We should see the slug when we click on the last 3 links.

Catch All Routes

We can add catch-all routes by adding ... before the file name.

For example, we can write:

pages/blog/[...slug].js:

import { useRouter } from 'next/router'
import Links from '../links';

const Post = () => {
  const router = useRouter()
  const { slug } = router.query

  return <div>
    <Links />
    <p>Post: {slug.join(', ')}</p>
  </div>
}

export default Post

pages/links.js

import Link from 'next/link'

const posts = [
  { slug: 'hello-world/1', title: 'hello world', id: 1 },
  { slug: 'good-news/2', title: 'good news', id: 2 },
  { slug: 'great-news/3', title: 'great news', id: 3 },
]

function Links() {
  return (
    <ul>
      <li>
        <Link href="/foo">
          <a>foo</a>
        </Link>
      </li>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
            <a>{post.title}</a>
          </Link>
        </li>
      ))}
    </ul>
  )
}

export default Links

We added an extra URL parameter to each slug .

They’re rendered as arrays in [...slug].js .

Therefore, we called join to convert the array into a comma-separated string.

Optional Catch-all Routes

We can make catch-all routes optional by adding extra brackets around the file name.

For example, we can write:

pages/blog/[[...slug]].js

import { useRouter } from 'next/router'
import Links from '../links';

const Post = () => {
  const router = useRouter()
  const { slug } = router.query

  return <div>
    <Links />
    <p>Post: {slug && slug.join(', ')}</p>
  </div>
}

export default Post

We wrapped an extra set of brackets around the file name.

Now we can go to the /blog path and we’ll see the same page without anything after Post: .

Conclusion

We can add dynamic route links with Next.js.

All we have to do is to follow the file name convention and use the href and as props with the Link component.