Build a Next.js Interactive Web-Based Simple Code Editor

Written by

in

Ever found yourself needing a quick way to test out a snippet of code, share a small program with a colleague, or simply practice your coding skills without the overhead of a full-fledged IDE? A web-based code editor comes to the rescue! In this tutorial, we’ll build a simple, yet functional, code editor using Next.js. This project is perfect for beginners and intermediate developers looking to expand their skills and understand how to create interactive web applications.

Why Build a Code Editor?

Code editors are indispensable tools for developers. They provide a space to write, test, and debug code. While sophisticated IDEs offer a wealth of features, a simple web-based editor provides several advantages:

  • Accessibility: Accessible from any device with a web browser.
  • Collaboration: Easy to share code snippets and collaborate with others.
  • Learning: A great way to practice coding and experiment with different languages.

Building a code editor helps you understand how to:

  • Handle user input and events.
  • Integrate with external libraries (like a code syntax highlighter).
  • Manage state in a React application.
  • Deploy a web application.

Prerequisites

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

  • Node.js and npm (or yarn): These are essential for managing your project dependencies and running the development server.
  • A code editor: (e.g., VS Code, Sublime Text, Atom) to write your code.
  • Basic understanding of HTML, CSS, and JavaScript: While this tutorial will guide you, familiarity with these languages is helpful.

Setting Up the Project

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

npx create-next-app code-editor

This command will create a new directory called code-editor with all the necessary files to get started. Navigate into your project directory:

cd code-editor

Now, let’s start the development server:

npm run dev

Open your browser and go to http://localhost:3000. You should see the default Next.js welcome page.

Installing Dependencies

For our code editor, we’ll use a code syntax highlighter to make the code more readable. We’ll use the react-syntax-highlighter library. Install it using npm:

npm install react-syntax-highlighter

This library will help us display the code with proper syntax highlighting for various programming languages.

Building the Code Editor UI

Let’s create the basic structure for our code editor. We’ll start by modifying the pages/index.js file.

Replace the content of pages/index.js with the following code:

import { useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

export default function Home() {
  const [code, setCode] = useState(
    '// Write your code herenconsole.log('Hello, world!');'
  );

  const handleCodeChange = (event) => {
    setCode(event.target.value);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', padding: '20px' }}>
      <textarea
        style={{
          width: '100%',
          height: '50%',
          padding: '10px',
          fontSize: '16px',
          fontFamily: 'monospace',
          border: '1px solid #ccc',
          borderRadius: '4px',
          resize: 'none', // Prevent resizing
        }}
        value={code}
        onChange={handleCodeChange}
      />
      <div style={{ marginTop: '20px', height: '50%', overflow: 'auto' }}>
        <SyntaxHighlighter language="javascript" style={dark} showLineNumbers={true} wrapLongLines={true}>
          {code}
        </SyntaxHighlighter>
      </div>
    </div>
  );
}

Let’s break down this code:

  • Import statements: We import useState from React and the necessary components from react-syntax-highlighter.
  • State: We use the useState hook to manage the code entered by the user. The initial value is a simple JavaScript console.log statement.
  • handleCodeChange function: This function updates the code state whenever the user types in the textarea.
  • JSX structure: We create a textarea for the user to input code and a div to display the highlighted code. The SyntaxHighlighter component from react-syntax-highlighter renders the code with syntax highlighting.

Now, let’s add some basic styling to make the editor look better.

Create a file named styles/globals.css in your project if it doesn’t already exist. Add the following CSS to it:

body {
  font-family: sans-serif;
  margin: 0;
  background-color: #f4f4f4;
}

This CSS sets the font and background color for the page. You can customize these styles to match your preferences.

Save the files and refresh your browser. You should now see a textarea where you can type code and a section below it that displays the code with syntax highlighting. Try typing some JavaScript code, and you’ll see the syntax highlighting in action. You’ll notice the code is displayed with line numbers and wraps long lines.

Adding Language Selection

Currently, the code editor only highlights JavaScript. Let’s add a dropdown menu to allow users to select the programming language.

Modify your pages/index.js file as follows:

import { useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

const languageOptions = [
  { value: 'javascript', label: 'JavaScript' },
  { value: 'python', label: 'Python' },
  { value: 'java', label: 'Java' },
  { value: 'csharp', label: 'C#' },
  { value: 'cpp', label: 'C++' },
  { value: 'go', label: 'Go' },
  { value: 'php', label: 'PHP' },
  { value: 'html', label: 'HTML' },
  { value: 'css', label: 'CSS' },
  { value: 'jsx', label: 'JSX' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'bash', label: 'Bash' },
  { value: 'sql', label: 'SQL' },
  // Add more languages as needed
];

export default function Home() {
  const [code, setCode] = useState(
    '// Write your code herenconsole.log('Hello, world!');'
  );
  const [language, setLanguage] = useState('javascript');

  const handleCodeChange = (event) => {
    setCode(event.target.value);
  };

  const handleLanguageChange = (event) => {
    setLanguage(event.target.value);
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', padding: '20px' }}>
      <div style={{ marginBottom: '10px' }}>
        <label htmlFor="language" style={{ marginRight: '10px' }}>Select Language:</label>
        <select id="language" value={language} onChange={handleLanguageChange}
                style={{ padding: '5px', borderRadius: '4px', border: '1px solid #ccc' }}>
          {languageOptions.map((option) => (
            <option key={option.value} value={option.value}>{option.label}</option>
          ))}
        </select>
      </div>
      <textarea
        style={{
          width: '100%',
          height: '50%',
          padding: '10px',
          fontSize: '16px',
          fontFamily: 'monospace',
          border: '1px solid #ccc',
          borderRadius: '4px',
          resize: 'none', // Prevent resizing
        }}
        value={code}
        onChange={handleCodeChange}
      />
      <div style={{ marginTop: '20px', height: '50%', overflow: 'auto' }}>
        <SyntaxHighlighter language={language} style={dark} showLineNumbers={true} wrapLongLines={true}>
          {code}
        </SyntaxHighlighter>
      </div>
    </div>
  );
}

Here’s what changed:

  • languageOptions array: We create an array containing objects, each representing a programming language and its corresponding label.
  • language state: We add a language state variable to store the selected language.
  • handleLanguageChange function: This function updates the language state when the user selects a different language from the dropdown.
  • Dropdown menu: We add a select element to let the user choose a programming language. The onChange event calls handleLanguageChange. We use the languageOptions array to populate the <option> elements.
  • Syntax Highlighter Language Prop: The SyntaxHighlighter component now uses the language state to determine which language to highlight.

Now, save the file and refresh your browser. You should see a dropdown menu above the textarea. When you select a different language, the syntax highlighting should update accordingly. You can test this by entering code in different languages like Python, Java, or HTML.

Adding a “Run” Button

Let’s add a button that allows the user to “run” the code and see the output. Since this is a simple example, we’ll use eval() to execute the code. Note: Using eval() in production is generally discouraged due to security risks. For a real-world application, you would use a more secure method like sending the code to a server to be executed.

Modify your pages/index.js file as follows:

import { useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

const languageOptions = [
  { value: 'javascript', label: 'JavaScript' },
  { value: 'python', label: 'Python' },
  { value: 'java', label: 'Java' },
  { value: 'csharp', label: 'C#' },
  { value: 'cpp', label: 'C++' },
  { value: 'go', label: 'Go' },
  { value: 'php', label: 'PHP' },
  { value: 'html', label: 'HTML' },
  { value: 'css', label: 'CSS' },
  { value: 'jsx', label: 'JSX' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'bash', label: 'Bash' },
  { value: 'sql', label: 'SQL' },
  // Add more languages as needed
];

export default function Home() {
  const [code, setCode] = useState(
    '// Write your code herenconsole.log('Hello, world!');'
  );
  const [language, setLanguage] = useState('javascript');
  const [output, setOutput] = useState('');

  const handleCodeChange = (event) => {
    setCode(event.target.value);
  };

  const handleLanguageChange = (event) => {
    setLanguage(event.target.value);
  };

  const handleRun = () => {
    try {
      // Using eval() for demonstration purposes.  Do NOT use in production.
      setOutput(eval(code));
    } catch (error) {
      setOutput(error.message);
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', padding: '20px' }}>
      <div style={{ marginBottom: '10px', display: 'flex', alignItems: 'center' }}>
        <label htmlFor="language" style={{ marginRight: '10px' }}>Select Language:</label>
        <select id="language" value={language} onChange={handleLanguageChange}
                style={{ padding: '5px', borderRadius: '4px', border: '1px solid #ccc', marginRight: '20px' }}>
          {languageOptions.map((option) => (
            <option key={option.value} value={option.value}>{option.label}</option>
          ))}
        </select>
        <button onClick={handleRun}
                style={{ padding: '5px 10px', borderRadius: '4px', backgroundColor: '#4CAF50', color: 'white', border: 'none', cursor: 'pointer' }}>Run</button>
      </div>
      <textarea
        style={{
          width: '100%',
          height: '50%',
          padding: '10px',
          fontSize: '16px',
          fontFamily: 'monospace',
          border: '1px solid #ccc',
          borderRadius: '4px',
          resize: 'none', // Prevent resizing
        }}
        value={code}
        onChange={handleCodeChange}
      />
      <div style={{ marginTop: '20px', height: 'calc(25% - 20px)', overflow: 'auto' }}>
        <SyntaxHighlighter language={language} style={dark} showLineNumbers={true} wrapLongLines={true}>
          {code}
        </SyntaxHighlighter>
      </div>
      <div style={{ marginTop: '20px', height: '25%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', overflow: 'auto' }}>
        <h3>Output:</h3>
        <pre>{typeof output === 'object' ? JSON.stringify(output, null, 2) : output}</pre>
      </div>
    </div>
  );
}

Here’s what changed:

  • output state: We add an output state to store the result of the code execution.
  • handleRun function: This function is called when the “Run” button is clicked. It uses a try...catch block to handle potential errors during code execution. Inside the try block, it calls eval(code) to execute the code. The result is then stored in the output state.
  • “Run” button: We add a button that calls the handleRun function when clicked.
  • Output display: We add a new div below the syntax highlighted code to display the output of the code execution. If the output is an object, we use JSON.stringify to format it nicely. We also added a pre tag for better formatting of the output.

Now, save the file and refresh your browser. You should see a “Run” button next to the language selector. When you type some JavaScript code (e.g., console.log('Hello');) and click the “Run” button, the output should appear in the output section below. If there are any errors, they will also be displayed there.

Adding Error Handling

Currently, the code editor only catches errors and displays the error message. Let’s improve the error handling by providing more specific error messages and highlighting the line where the error occurred.

This is a more advanced feature and will require more complex logic. For simplicity, we will only enhance the display of the error message.

Modify your pages/index.js file as follows:

import { useState } from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

const languageOptions = [
  { value: 'javascript', label: 'JavaScript' },
  { value: 'python', label: 'Python' },
  { value: 'java', label: 'Java' },
  { value: 'csharp', label: 'C#' },
  { value: 'cpp', label: 'C++' },
  { value: 'go', label: 'Go' },
  { value: 'php', label: 'PHP' },
  { value: 'html', label: 'HTML' },
  { value: 'css', label: 'CSS' },
  { value: 'jsx', label: 'JSX' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'bash', label: 'Bash' },
  { value: 'sql', label: 'SQL' },
  // Add more languages as needed
];

export default function Home() {
  const [code, setCode] = useState(
    '// Write your code herenconsole.log('Hello, world!');'
  );
  const [language, setLanguage] = useState('javascript');
  const [output, setOutput] = useState('');

  const handleCodeChange = (event) => {
    setCode(event.target.value);
  };

  const handleLanguageChange = (event) => {
    setLanguage(event.target.value);
  };

  const handleRun = () => {
    try {
      // Using eval() for demonstration purposes.  Do NOT use in production.
      setOutput(eval(code));
    } catch (error) {
      setOutput(`Error: ${error.message}`);
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', padding: '20px' }}>
      <div style={{ marginBottom: '10px', display: 'flex', alignItems: 'center' }}>
        <label htmlFor="language" style={{ marginRight: '10px' }}>Select Language:</label>
        <select id="language" value={language} onChange={handleLanguageChange}
                style={{ padding: '5px', borderRadius: '4px', border: '1px solid #ccc', marginRight: '20px' }}>
          {languageOptions.map((option) => (
            <option key={option.value} value={option.value}>{option.label}</option>
          ))}
        </select>
        <button onClick={handleRun}
                style={{ padding: '5px 10px', borderRadius: '4px', backgroundColor: '#4CAF50', color: 'white', border: 'none', cursor: 'pointer' }}>Run</button>
      </div>
      <textarea
        style={{
          width: '100%',
          height: '50%',
          padding: '10px',
          fontSize: '16px',
          fontFamily: 'monospace',
          border: '1px solid #ccc',
          borderRadius: '4px',
          resize: 'none', // Prevent resizing
        }}
        value={code}
        onChange={handleCodeChange}
      />
      <div style={{ marginTop: '20px', height: 'calc(25% - 20px)', overflow: 'auto' }}>
        <SyntaxHighlighter language={language} style={dark} showLineNumbers={true} wrapLongLines={true}>
          {code}
        </SyntaxHighlighter>
      </div>
      <div style={{ marginTop: '20px', height: '25%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px', overflow: 'auto' }}>
        <h3>Output:</h3>
        <pre>{typeof output === 'object' ? JSON.stringify(output, null, 2) : output}</pre>
      </div>
    </div>
  );
}

In this example, the error message is simply prepended with “Error: “.

For more advanced error handling, you would need to parse the error message and potentially use a library like Acorn to parse the code and pinpoint the exact line number where the error occurred. This is beyond the scope of this beginner-friendly tutorial.

Deploying Your Code Editor

Once you’ve built your code editor, you’ll likely want to deploy it so others can use it. Next.js makes deployment straightforward. Here are a couple of popular options:

  • Vercel: Vercel is the company behind Next.js, and deploying to Vercel is incredibly easy. Simply push your code to a Git repository (like GitHub or GitLab), and Vercel will automatically build and deploy your application.
  • Netlify: Netlify is another popular platform for deploying web applications. It also offers automatic builds and deployments from Git repositories.
  • Other Platforms: You can also deploy your Next.js application to other platforms like AWS, Google Cloud, or Azure.

The specific steps for deploying to each platform will vary, but they generally involve connecting your Git repository and configuring the build settings.

Common Mistakes and How to Fix Them

Here are some common mistakes beginners make when building a Next.js code editor and how to fix them:

  • Incorrect Syntax Highlighting: Ensure you’ve installed the react-syntax-highlighter package and imported the correct styles. Double-check that you’ve specified the correct language in the SyntaxHighlighter component’s language prop.
  • Code Not Running: Make sure you’ve correctly implemented the “Run” button functionality. Remember that using eval() is generally discouraged in production environments. Consider using a more secure method for executing code.
  • UI Issues: Pay attention to the layout and styling of your editor. Use CSS to properly arrange the textarea, syntax-highlighted code, and output sections. Consider using a CSS framework like Tailwind CSS or Bootstrap to speed up the styling process.
  • State Management Issues: Ensure your state variables (e.g., code, language, output) are updated correctly. Double-check that the onChange events are correctly connected to the state update functions (e.g., setCode, setLanguage).
  • Deployment Errors: If you encounter deployment errors, carefully review the build logs provided by your deployment platform. Common issues include missing dependencies or incorrect build configurations.

Key Takeaways

  • You’ve learned how to create a basic web-based code editor using Next.js.
  • You’ve seen how to use react-syntax-highlighter for code syntax highlighting.
  • You’ve learned how to handle user input (textarea) and events (button clicks).
  • You’ve explored how to use state management with the useState hook.
  • You’ve understood the basic steps involved in deploying a Next.js application.

FAQ

Here are some frequently asked questions about building a code editor in Next.js:

  1. How can I add more programming languages to the editor? You can add more languages by adding them to the languageOptions array and ensuring that the react-syntax-highlighter library supports those languages. You may need to import additional styles or language-specific components from the library.
  2. How do I handle errors more effectively? For more robust error handling, consider using a code parser (like Acorn) to analyze the code and pinpoint the exact line where the error occurred. This will allow you to provide more specific and helpful error messages.
  3. Can I save and load code? Yes, you can add functionality to save and load code using local storage or by integrating with a backend server to store code in a database.
  4. How can I add features like autocompletion and code formatting? You can integrate libraries like CodeMirror or Monaco Editor to add features like autocompletion, code formatting, and other advanced editing capabilities.

Building a code editor is a fantastic way to enhance your Next.js and React skills. While this tutorial covers the fundamental aspects, there’s ample room for expansion. You can incorporate features like saving and loading code, implementing more advanced error handling, adding autocompletion, or integrating with a backend for code execution and storage. The possibilities are endless. Keep experimenting, and enjoy the process of building and learning!