14 Exercise: Loading assets in a Next.js application

14.1 Task

Create a simple Next.js application that loads assets in the form of images and CSS files and displays them on a web page.

14.1.1 Requirements

  1. create a new Next.js project using Create Next App.

  2. add at least two images (e.g., image1.jpg and image2.jpg) and one CSS file (styles.css) to the public directory of your project.

  3. create a React component called AssetGallery that displays the two images from the public directory on the web page.

  4. use CSS to customize the appearance of the images. Make sure that the images are displayed side by side.

  5. create a simple navigation to switch between the images. For example, buttons or arrows to switch between image1.jpg and image2.jpg.

  6. implement a function or method to switch between the images when the user clicks on the buttons or arrows.

14.1.2 Sample solution

Here is a sample solution that fulfills the above requirements:

// pages/index.js

import React, { useState } from 'react';
import Image from 'next/image';
import styles from '../styles/Home.module.css';

const images = ['image1.jpg', 'image2.jpg']; // List of images

export default function Home() {
  const [currentImageIndex, setCurrentImageIndex] = useState(0);

  const handlePrevious = () => {
    if (currentImageIndex > 0) {
      setCurrentImageIndex(currentImageIndex - 1);
    }
  };

  const handleNext = () => {
    if (currentImageIndex < images.length - 1) {
      setCurrentImageIndex(currentImageIndex + 1);
    }
  };

  return (
    <div className={styles.container}>
      <h1>Asset Gallery</h1>
      <div className={styles.gallery}>
        <button onClick={handlePrevious} disabled={currentImageIndex === 0}>
          Previous
        </button>
        <image
          src={`/images/${images[currentImageIndex]}`}
          alt={`image ${currentImageIndex + 1}`}
          width={300}
          height={200}
        />
        <button onClick={handleNext} disabled={currentImageIndex === images.length - 1}>
          Next
        </button>
      </div>
    </div>
  );
}

In this sample solution, a simple Next.js application is created that loads two images from the public directory and displays them on a web page. Users can switch between the images using the “Previous” and “Next” buttons. The CSS styles can be defined in the styles.css file in the styles directory to customize the appearance of the images.

14.2 Loading Markdown files in Next.js

Loading and processing Markdown files in Next.js applications is a common use case, especially for blogs, documentation pages or CMS-based websites. Here are the key steps for integrating Markdown into Next.js:

14.2.1 1. installing necessary packages

Additional packages are required to process Markdown files. Typical packages are remark, remark-html and gray-matter. They can be installed with npm or yarn:

npm install remark remark-html gray-matter

14.2.2 2. prepare markdown files

Markdown files are usually stored in the content or pages directory. The file extension is .md or .mdx for Markdown with JSX support.

14.2.3 3. import Markdown files

In Next.js, you can use the Node.js file system module (fs) to read in Markdown files. For example, you can list and read in all files in a directory:

import fs from 'fs';
import path from 'path';

const postsDirectory = path.join(process.cwd(), 'content');

export function getSortedPostsData() {
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map(fileName => {
    // Further processing steps
  });
  return allPostsData;
}

14.2.4 4. parsing Markdown content

With gray-matter you can extract the frontmatter and the content of the Markdown files. Then use remark and remark-html to convert the Markdown content into HTML:

import matter from 'gray-matter';
import { remark } from 'remark';
import html from 'remark-html';

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');

  const matterResult = matter(fileContents);

  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const contentHtml = processedContent.toString();

  return {
    id,
    contentHtml,
    ...matterResult.data,
  };
}

14.2.5 5. rendering the content

The processed Markdown content can then be rendered in Next.js components by using dangerouslySetInnerHTML or by using a suitable Markdown-to-React component.

The integration of Markdown in Next.js enables flexible content management. By using Node.js and additional packages, developers can efficiently process Markdown content and integrate it into their Next.js applications.

14.3 Exercise: Integrating Markdown files into Next.js

14.3.1 Task

Create a Next.js application that reads Markdown files from a directory, converts the content to HTML and displays it on a web page. The application should display a list of blog posts that have been read from the Markdown files and allow the user to access individual posts.

14.3.2 Steps to the solution

  1. create a new Next.js project:

    npx create-next-app markdown-blog
    cd markdown-blog
  2. install the required packages:

    npm install remark remark-html gray-matter
  3. create a directory content in the root directory of your project. Create some Markdown files for blog posts in it, e.g. post-1.md, post-2.md, etc.

  4. create a pages folder in the root directory and create an index.js file in it. This will be the main page of your application.

  5. implement the getSortedPostsData and getPostData functions as per the previous answer to read and process the Markdown files.

  6. create a page that displays the list of blog posts. Here is an example of pages/index.js:

    import Link from 'next/link';
    import { getSortedPostsData } from '../lib/posts';
    
    export default function Home({ allPostsData }) {
      return (
        <div>
          <h1>Blog</h1>
          <ul>
            {allPostsData.map(({ id, date, title }) => (
              <li key={id}>
                <link href={`/posts/${id}`}>
                  <a>{title}</a>
                </link>
                <br />
                {date}
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export async function getStaticProps() {
      const allPostsData = getSortedPostsData();
      return {
        props: {
          allPostsData,
        },
      };
    }
  7. create another page pages/posts/[id].js that displays individual blog posts. Here is an example:

    import { getPostData } from '../../lib/posts';
    import marked from 'marked';
    
    export default function Post({ postData }) {
      return (
        <div>
          <h1>{postData.title}</h1>
          <div dangerouslySetInnerHTML={{ __html: marked(postData.contentHtml) }} />
        </div>
      );
    }
    
    export async function getStaticPaths() {
      // The paths to the Markdown files should be generated here
    }
    
    export async function getStaticProps({ params }) {
      const postData = await getPostData(params.id);
      return {
        props: {
          postData,
        },
      };
    }
  1. Implement the getStaticPaths functions in pages/posts/[id].js to dynamically generate the paths to each blog post.
  1. make sure you add the CSS styles and layouts as needed to make your blog posts look appealing.

  2. run your Next.js application with npm run dev and check that it works correctly.

14.4 Handling forms in Next.js

Forms are a central component of many web applications. In Next.js, forms are handled similarly to traditional React applications, with some specific features offered by the framework.

14.4.1 1. creating a form

The creation of a form in Next.js begins with the definition of the form structure in JSX. Conventional HTML form elements such as <input>, <textarea> and <select> can be used here. For example:

<form onSubmit={handleSubmit}>
  <label htmlFor="name">Name:</label>
  <input type="text" id="name" name="name" required />

  <label htmlFor="email">Email:</label>
  <input type="email" id="email" name="email" required />

  <button type="submit">Send</button>
</form>

14.4.2 2. management of the form status

The form state can be managed using React hooks such as useState. Each form element is associated with a corresponding state. For example:

const [name, setName] = useState('');
const [email, setEmail] = useState('');

const handleInputChange = (e) => {
  const { name, value } = e.target;
  if (name === 'name') setName(value);
  if (name === 'email') setEmail(value);
};

14.4.3 3. form submit handling

The function handleSubmit is called when the form is submitted. This function can be asynchronous, for example to send data to a server:

const handleSubmit = async (e) => {
  e.preventDefault();
  const formData = { name, email };
  // An API call could be made here
};

14.4.4 4. server-side processing of form data

Next.js makes it possible to process form data on the server side. This can be done via API routes that are defined within the pages/api directory. These routes act as endpoints for HTTP requests.

14.4.5 5. validation of form data

Form data can be validated on the client side in the submit function or on the server side in an API route. Libraries such as Yup or Formik can be used for more complex validations.