Build a Simple Next.js Interactive Web-Based Markdown Previewer

Written by

in

In the world of web development, the ability to quickly preview Markdown content is a valuable skill. Whether you’re a writer, a developer documenting code, or simply someone who enjoys the clean formatting of Markdown, having a live preview tool can significantly enhance your workflow. This tutorial will guide you through building a simple, interactive Markdown previewer using Next.js, a popular React framework known for its performance and developer-friendly features. We’ll explore the core concepts, step-by-step implementation, and address common pitfalls to ensure you can confidently create your own Markdown previewer.

Why Build a Markdown Previewer?

Markdown is a lightweight markup language that allows you to format text using plain text syntax. It’s widely used for writing documentation, creating README files, and authoring content on platforms like GitHub, Reddit, and many blogging platforms. A Markdown previewer provides an instant visual representation of your Markdown content, showing you how it will appear when rendered. This is crucial for:

  • Accuracy: Ensuring your formatting is correct before publishing.
  • Efficiency: Saving time by avoiding the need to repeatedly render your Markdown elsewhere.
  • Collaboration: Enabling easier review and feedback on Markdown documents.

Building a Markdown previewer is also a great learning project for Next.js, allowing you to practice fundamental concepts like state management, component composition, and handling user input.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js and npm (or yarn): These are essential for managing JavaScript packages and running Next.js. You can download them from nodejs.org.
  • A code editor: Visual Studio Code, Sublime Text, or any editor you prefer.
  • Basic knowledge of HTML, CSS, and JavaScript: Familiarity with these languages will be helpful, but we’ll explain the key concepts as we go.

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app markdown-previewer
cd markdown-previewer

This command creates a new Next.js project named “markdown-previewer” and navigates you into the project directory. Next.js sets up a basic project structure with essential files and configurations.

Installing Dependencies

Our Markdown previewer will need a library to convert Markdown text into HTML. We’ll use the ‘marked’ library, which is a popular and reliable choice. Install it using npm or yarn:

npm install marked
# or
yarn add marked

This command installs the ‘marked’ package and adds it to your project’s dependencies.

Project Structure

Your project directory should have a structure similar to this:


markdown-previewer/
├── node_modules/
├── pages/
│   └── index.js
├── public/
├── .gitignore
├── next.config.js
├── package-lock.json
├── package.json
└── README.md

The `pages` directory is where we’ll create our components for the different pages of our application. The `index.js` file inside the `pages` directory represents the home page of our application.

Building the Markdown Previewer Component

Now, let’s create the core component for our previewer. Open `pages/index.js` and replace the existing code with the following:

import { useState } from 'react';
import { marked } from 'marked';

export default function Home() {
  const [markdown, setMarkdown] = useState('');

  const handleInputChange = (event) => {
    setMarkdown(event.target.value);
  };

  const html = marked.parse(markdown);

  return (
    <div className="container">
      <div className="input-container">
        <textarea
          className="input"
          value={markdown}
          onChange={handleInputChange}
          placeholder="Enter Markdown here..."
        />
      </div>
      <div className="preview-container">
        <div className="preview" dangerouslySetInnerHTML={{ __html: html }} />
      </div>
      <style jsx>
        {`
          .container {
            display: flex;
            flex-direction: column;
            align-items: stretch;
            padding: 20px;
            font-family: sans-serif;
          }

          .input-container, .preview-container {
            flex: 1;
            padding: 10px;
            border: 1px solid #ccc;
            margin-bottom: 10px;
          }

          .input {
            width: 100%;
            height: 300px;
            padding: 10px;
            font-family: monospace;
            font-size: 14px;
            border: 1px solid #ddd;
            resize: vertical;
          }

          .preview {
            padding: 10px;
            border: 1px solid #ddd;
            background-color: #f9f9f9;
            overflow-x: auto; /* Handle horizontal overflow */
          }

          @media (min-width: 768px) {
            .container {
              flex-direction: row;
            }

            .input-container, .preview-container {
              flex: 1;
              margin: 10px;
            }

            .input {
              height: 400px;
            }
          }
        `}
      </style>
    </div>
  );
}

Let’s break down this code:

  • Import Statements: We import `useState` from React for managing the state of the Markdown input and `marked` from the ‘marked’ library to parse the Markdown.
  • State Management: `const [markdown, setMarkdown] = useState(”);` initializes a state variable called `markdown` with an empty string. This variable will hold the Markdown text entered by the user. `setMarkdown` is the function we use to update the `markdown` state.
  • `handleInputChange` Function: This function is triggered whenever the user types in the textarea. It updates the `markdown` state with the current value of the textarea.
  • Markdown Parsing: `const html = marked.parse(markdown);` uses the `marked` library to convert the `markdown` string into HTML.
  • JSX Structure: The return statement defines the structure of our component using JSX (JavaScript XML). It includes:
    • A `textarea` for the user to enter Markdown. The `value` attribute is bound to the `markdown` state, and the `onChange` event is tied to the `handleInputChange` function.
    • A `div` to display the rendered HTML. The `dangerouslySetInnerHTML` prop is used to inject the HTML generated by `marked.parse()`. Important: Be cautious when using `dangerouslySetInnerHTML` as it can potentially expose your application to cross-site scripting (XSS) vulnerabilities if the input is not properly sanitized. In this case, since we are controlling the input, it is acceptable.
  • Styling: The `<style jsx>` block contains CSS styles to layout and style the components. We have basic styles for the container, input area, and preview area. Note the use of media queries for responsiveness.

Running Your Application

To run your application, execute the following command in your terminal:

npm run dev
# or
yarn dev

This starts the Next.js development server. Open your web browser and navigate to `http://localhost:3000` (or the address shown in your terminal). You should see your Markdown previewer, with an input area on the left and a preview area on the right. Type some Markdown in the input area, and you should see the rendered HTML in the preview area.

Understanding the Code in Detail

State Management with `useState`

The `useState` hook is a fundamental concept in React for managing component state. In our example, we use it to store the Markdown text entered by the user. When the user types in the textarea, the `handleInputChange` function is called, which updates the `markdown` state using `setMarkdown`. This triggers a re-render of the component, and the updated `markdown` value is used to generate the HTML for the preview.

Handling User Input

The `onChange` event handler on the `textarea` is crucial for capturing user input. Every time the user types a character, the `handleInputChange` function is executed. This function receives an `event` object, and `event.target.value` contains the current value of the textarea. We then use `setMarkdown(event.target.value)` to update the `markdown` state with this new value.

Markdown Parsing with `marked`

The `marked` library is responsible for converting the Markdown text into HTML. The `marked.parse(markdown)` function takes the Markdown string as input and returns the corresponding HTML. This HTML is then displayed in the preview area using the `dangerouslySetInnerHTML` prop.

Styling with CSS-in-JS (jsx)

Next.js allows you to write CSS directly within your JavaScript components using the `<style jsx>` tag. This approach, known as CSS-in-JS, keeps your styles encapsulated within your components, making them easier to manage and preventing style conflicts. In our example, we use CSS-in-JS to style the input area, preview area, and overall layout of the previewer. We also use media queries to make the previewer responsive to different screen sizes.

Common Mistakes and How to Fix Them

1. Not Installing the `marked` Library

Mistake: Forgetting to install the `marked` library using `npm install marked` or `yarn add marked`. This will result in an error when you try to import and use the `marked` function.

Solution: Make sure you install the library before using it. Run `npm install marked` in your terminal, and then restart your development server if necessary.

2. Incorrectly Using `dangerouslySetInnerHTML`

Mistake: Using `dangerouslySetInnerHTML` without understanding its implications. This can lead to XSS vulnerabilities if you’re not careful about the source of the HTML. If you are taking user input for the markdown, then it needs to be sanitized before parsing to HTML.

Solution: In our case, we are in control of the input, but be extremely cautious when using this prop. If you are displaying HTML from an untrusted source, consider using a library like `dompurify` to sanitize the HTML and prevent XSS attacks.

3. Not Handling Empty Input

Mistake: The preview area might show blank or unexpected results when the input is empty or contains only whitespace.

Solution: You can add a check in your component to handle empty input. For example, you could display a default message like “Enter Markdown to preview” when the `markdown` state is empty.


  const html = markdown ? marked.parse(markdown) : "<p>Enter Markdown to preview</p>";

4. Styling Issues

Mistake: The preview area might not render the Markdown correctly due to CSS conflicts or missing styles. This is a common issue when rendering HTML generated from external sources.

Solution: Use CSS reset or normalize styles to provide consistent rendering across different browsers. Also, review the HTML output generated by `marked` and ensure the CSS styles are correctly applied to the elements.

Enhancements and Next Steps

This is a basic Markdown previewer, but there are many ways to enhance it:

  • Syntax Highlighting: Integrate a syntax highlighting library (e.g., Prism.js, highlight.js) to highlight code blocks in your Markdown.
  • Toolbar: Add a toolbar with buttons for common Markdown formatting options (bold, italics, headings, etc.).
  • Live Preview for Images: Allow users to upload images and see them rendered in the preview.
  • Error Handling: Implement error handling to gracefully handle invalid Markdown input.
  • Customization Options: Allow users to customize the preview style (e.g., font, colors, theme).
  • Local Storage: Save the Markdown content to local storage so that it persists across sessions.

Key Takeaways

Building a Markdown previewer with Next.js is a practical way to learn about state management, component composition, and handling user input. The ‘marked’ library simplifies the Markdown parsing process, allowing you to focus on the front-end development. By following this tutorial, you’ve created a functional Markdown previewer and gained valuable experience with Next.js and web development fundamentals.

FAQ

1. How do I handle images in my Markdown?

To display images in your Markdown, you use the standard Markdown syntax: `![alt text](image_url)`. The `marked` library will convert this to an `<img>` tag. Ensure that your image URLs are accessible from your web application. You may need to handle image uploads and storage separately.

2. Can I use this previewer with a different Markdown library?

Yes, you can easily swap out the `marked` library with another Markdown parsing library, such as `markdown-it`. You would simply need to install the new library and update the import statement and the parsing function call in your component.

3. How can I make the preview area more visually appealing?

You can customize the appearance of the preview area by adding CSS styles. Consider using a CSS framework like Bootstrap or Tailwind CSS to quickly style the elements. You can also create a custom CSS theme that matches your website’s design.

4. How do I deploy this application?

You can deploy your Next.js application to various platforms, such as Vercel (which is recommended), Netlify, or AWS. The deployment process typically involves pushing your code to a Git repository and configuring the deployment platform to build and deploy your application. Vercel provides excellent support for Next.js applications, making deployment very straightforward.

The ability to create a Markdown previewer is a valuable skill in modern web development. This tutorial provides a solid foundation for building interactive web applications using Next.js, and understanding the core concepts of state management, user input handling, and component rendering will serve you well in future projects. Experiment with the enhancements suggested above to further refine your skills and create a more feature-rich previewer. The possibilities are vast, and with a little creativity, you can transform this simple tool into something truly remarkable.