Build a Simple Next.js Interactive File Renamer App

Written by

in

In the digital age, we’re constantly managing files. Whether it’s organizing documents, renaming images, or preparing files for a project, the ability to quickly and efficiently rename multiple files can save a significant amount of time and frustration. Imagine having a collection of photos downloaded from your camera, all with generic names like ‘IMG_0001.jpg,’ ‘IMG_0002.jpg,’ and so on. Renaming each file manually is a tedious task. This is where a file renamer application becomes invaluable.

Why Build a File Renamer App with Next.js?

Next.js is a powerful React framework that offers a lot of benefits for web development, especially for projects that benefit from SEO and fast performance. Here’s why it’s a great choice for this project:

  • Server-Side Rendering (SSR) and Static Site Generation (SSG): Next.js allows you to render your application on the server or generate static pages at build time. This improves SEO and initial load times, making your app more user-friendly.
  • Easy Routing: Next.js simplifies routing with its file-system-based routing. You don’t need to configure complex routing setups.
  • API Routes: Next.js provides a simple way to create API endpoints within your project, making it easy to handle file operations.
  • Optimized Performance: Next.js automatically optimizes images, code splitting, and more, resulting in a fast and responsive application.
  • Modern React Development: You’ll be using React, a popular and versatile JavaScript library, making the project easier to learn and maintain.

Project Overview: What We’ll Build

In this tutorial, we will create a simple, interactive file renamer application using Next.js. This application will allow users to upload multiple files, specify a renaming pattern, and then rename the files based on that pattern. The application will handle both the front-end (user interface) and the back-end (file processing) using Next.js features.

Key Features:

  • File Upload: Users can upload multiple files at once.
  • Renaming Pattern Input: Users can define a renaming pattern (e.g., “image_[index].[extension]”).
  • Preview: The application will display a preview of how the files will be renamed.
  • File Renaming: The application will rename the files on the server.
  • Download: Users can download the renamed files as a ZIP archive.

Prerequisites

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

  • Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn installed on your system. You can download them from nodejs.org.
  • A Code Editor: A code editor like Visual Studio Code, Sublime Text, or Atom.
  • Basic Understanding of JavaScript and React: Familiarity with JavaScript and React will be helpful.

Step-by-Step Instructions

1. Setting Up the Next.js Project

First, create a new Next.js project using the following command in your terminal:

npx create-next-app file-renamer-app

Navigate into your project directory:

cd file-renamer-app

Start the development server:

npm run dev

This will start the development server, and you can access your application at http://localhost:3000.

2. Installing Dependencies

We’ll need a few dependencies for our project:

  • jszip: For creating ZIP archives of the renamed files.
  • formidable: For handling file uploads in the backend.

Install them using npm:

npm install jszip formidable

3. Creating the Front-End (User Interface)

Let’s design the user interface. We’ll modify the `pages/index.js` file to include a file upload input, a text field for the renaming pattern, a preview section, and a button to trigger the renaming process.

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

import { useState } from 'react';

export default function Home() {
  const [files, setFiles] = useState([]);
  const [renamingPattern, setRenamingPattern] = useState('');
  const [preview, setPreview] = useState([]);
  const [renamedFiles, setRenamedFiles] = useState([]);
  const [downloadLink, setDownloadLink] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const handleFileChange = (e) => {
    setFiles(Array.from(e.target.files));
  };

  const generatePreview = () => {
    if (!files || files.length === 0 || !renamingPattern) {
      setPreview([]);
      return;
    }

    const previews = files.map((file, index) => {
      const extension = file.name.split('.').pop();
      const baseName = file.name.slice(0, file.name.lastIndexOf('.'));
      const newName = renamingPattern
        .replace('[index]', String(index + 1))
        .replace('[name]', baseName)
        .replace('[extension]', extension);
      return { oldName: file.name, newName: newName };
    });
    setPreview(previews);
  };

  const handleRename = async () => {
    if (!files || files.length === 0 || !renamingPattern) {
      setError('Please upload files and enter a renaming pattern.');
      return;
    }
    setError('');
    setLoading(true);
    const formData = new FormData();
    files.forEach((file) => {
      formData.append('files', file);
    });
    formData.append('renamingPattern', renamingPattern);

    try {
      const response = await fetch('/api/rename', {
        method: 'POST',
        body: formData,
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const data = await response.json();
      setRenamedFiles(data.renamedFiles);
      setDownloadLink(data.downloadLink);
    } catch (err) {
      console.error('Rename error:', err);
      setError('An error occurred during renaming.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <h2>File Renamer</h2>
      {error && <p style="{{">{error}</p>}
      
       setRenamingPattern(e.target.value)}
        onKeyUp={generatePreview}
      />
      <div>
        <h3>Preview</h3>
        {preview.map((item, index) => (
          <p>
            {item.oldName} -> {item.newName}
          </p>
        ))}
      </div>
      <button disabled="{loading}">
        {loading ? 'Renaming...' : 'Rename Files'}
      </button>
      {downloadLink && (
        <a href="{downloadLink}">
          Download Renamed Files
        </a>
      )}
      <div>
        <h3>Renamed Files (Server Side)</h3>
        {renamedFiles.map((item, index) => (
          <p>
            {item.oldName} -> {item.newName}
          </p>
        ))}
      </div>
    </div>
  );
}

This code does the following:

  • Imports the `useState` hook to manage the component’s state.
  • Sets up state variables: `files` (uploaded files), `renamingPattern` (the renaming pattern entered by the user), `preview` (a preview of the renamed files), `renamedFiles` (renamed files from the server), `downloadLink` (link for downloading the ZIP file), `loading` (indicates if the renaming process is in progress), and `error` (to display any errors).
  • `handleFileChange` function: Updates the `files` state when files are uploaded.
  • `generatePreview` function: Creates a preview of the new file names based on the input pattern and the uploaded file names.
  • `handleRename` function: Sends the files and renaming pattern to the server-side API endpoint for renaming. It also manages loading states and error messages.
  • Renders the UI: Includes an input for file uploads, a text input for the renaming pattern, a preview section, a button to trigger the renaming, and a download link after the files are renamed.

4. Creating the Back-End (API Routes)

Now, let’s create the API route to handle the file renaming and ZIP creation. Create a new file named `pages/api/rename.js` in your project’s `pages/api/` directory. This file will contain the server-side logic.

Add the following code to `pages/api/rename.js`:

import formidable from 'formidable';
import fs from 'fs';
import path from 'path';
import JSZip from 'jszip';

export const config = {
  api: {
    bodyParser: false, // Disable built-in body parser
  },
};

const renameFiles = async (files, renamingPattern) => {
  const renamedFiles = [];
  for (const file of files) {
    const extension = file.originalFilename.split('.').pop();
    const baseName = file.originalFilename.slice(0, file.originalFilename.lastIndexOf('.'));
    const index = files.indexOf(file) + 1;
    const newName = renamingPattern
      .replace('[index]', String(index))
      .replace('[name]', baseName)
      .replace('[extension]', extension);
    renamedFiles.push({ oldName: file.originalFilename, newName: newName });
  }
  return renamedFiles;
};

const handler = async (req, res) => {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' });
  }

  try {
    const form = new formidable.IncomingForm();
    form.uploadDir = './public/uploads'; // Temporary upload directory
    form.keepExtensions = true; // Keep original file extensions
    form.parse(req, async (err, fields, files) => {
      if (err) {
        console.error('Formidable error:', err);
        return res.status(500).json({ message: 'File upload failed.' });
      }

      const uploadedFiles = Object.values(files).flat();
      const renamingPattern = fields.renamingPattern;
      const renamedFiles = await renameFiles(uploadedFiles, renamingPattern);
      const zip = new JSZip();

      for (const file of uploadedFiles) {
        const newName = renamedFiles.find(item => item.oldName === file.originalFilename)?.newName;
        const filePath = path.join(form.uploadDir, file.newFilename);
        const fileContent = fs.readFileSync(filePath);
        zip.file(newName, fileContent);
      }

      const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' });
      const timestamp = Date.now();
      const zipFileName = `renamed_files_${timestamp}.zip`;
      const zipFilePath = path.join('./public/downloads', zipFileName);

      // Ensure the downloads directory exists
      if (!fs.existsSync('./public/downloads')) {
        fs.mkdirSync('./public/downloads', { recursive: true });
      }

      fs.writeFileSync(zipFilePath, zipBuffer);
      const downloadLink = `/downloads/${zipFileName}`;

      // Clean up temporary uploaded files
      uploadedFiles.forEach(file => {
        const filePath = path.join(form.uploadDir, file.newFilename);
        fs.unlink(filePath, (err) => {
          if (err) {
            console.error('Error deleting file:', err);
          }
        });
      });

      res.status(200).json({ renamedFiles: renamedFiles, downloadLink: downloadLink });
    });
  } catch (error) {
    console.error('API error:', error);
    res.status(500).json({ message: 'An unexpected error occurred.' });
  }
};

export default handler;

This code does the following:

  • Imports necessary modules: `formidable` for handling file uploads, `fs` for file system operations, `path` for working with file paths, and `jszip` for creating ZIP archives.
  • `config` object: Disables the built-in body parser of Next.js because we’re using `formidable` to handle the file uploads.
  • `renameFiles` function: Takes an array of files and the renaming pattern as input and returns an array of objects containing the old and new file names.
  • `handler` function: This is the main function that handles the API request.
  • Handles POST requests only: Checks if the request method is POST; otherwise, it returns a 405 Method Not Allowed error.
  • Parses the form data: Uses `formidable` to parse the incoming form data. It sets the upload directory to `./public/uploads` and keeps the original file extensions.
  • Renames the files: It calls the `renameFiles` function to generate new file names based on the pattern and the original file names.
  • Creates a ZIP archive: It creates a ZIP archive using `jszip`, adds each renamed file to the archive, and generates a buffer for the ZIP file.
  • Saves the ZIP file: Writes the ZIP file to the `./public/downloads` directory.
  • Provides a download link: Returns a JSON response containing the renamed file names and the download link for the ZIP file.
  • Error Handling: Includes error handling for file upload failures and unexpected errors.

5. Implementing the Renaming Logic

The core of the application is the renaming logic. This is where we take the user’s input (the renaming pattern) and apply it to the uploaded file names. The renaming pattern uses placeholders that are replaced with the index of the file and the original file name/extension.

The `renameFiles` function in the API route (`pages/api/rename.js`) handles this, as shown in the previous section. Here’s how it works:

  1. Iterates through the uploaded files: For each file, it extracts the file extension and base name.
  2. Replaces placeholders: It replaces the placeholders in the renaming pattern with the corresponding values. The available placeholders are:
    • `[index]`: Replaced with the file’s index (starting from 1).
    • `[name]`: Replaced with the original file name (without extension).
    • `[extension]`: Replaced with the file extension.
  3. Creates new file names: Based on these replacements, the function creates the new file names.

For example, if the user uploads a file named “document.pdf” and enters the pattern “report_[index]_[name].[extension]”, the renamed file name would be “report_1_document.pdf”.

6. Adding Styling (Optional)

To make the application visually appealing, you can add some basic styling. You can add CSS to the `pages/index.js` file or create a separate CSS file. Below is an example of some basic styling that you can add to the `pages/index.js` file:


import { useState } from 'react';

export default function Home() {
  // ... (previous code)

  return (
    <div style="{{">
      <h2 style="{{">File Renamer</h2>
      {error && <p style="{{">{error}</p>}
      <div style="{{">
        
      </div>
      <div style="{{">
         setRenamingPattern(e.target.value)}
          onKeyUp={generatePreview}
          style={{ padding: '8px', width: '100%', maxWidth: '300px', marginBottom: '5px' }}
        />
      </div>
      <div style="{{">
        <h3 style="{{">Preview</h3>
        {preview.map((item, index) => (
          <p style="{{">
            {item.oldName} -> {item.newName}
          </p>
        ))}
      </div>
      <button disabled="{loading}" style="{{">
        {loading ? 'Renaming...' : 'Rename Files'}
      </button>
      {downloadLink && (
        <div style="{{">
          <a href="{downloadLink}" style="{{">
            Download Renamed Files
          </a>
        </div>
      )}
      <div style="{{">
        <h3 style="{{">Renamed Files (Server Side)</h3>
        {renamedFiles.map((item, index) => (
          <p style="{{">
            {item.oldName} -> {item.newName}
          </p>
        ))}
      </div>
    </div>
  );
}

This adds some basic styling for better readability and a more polished look.

7. Testing the Application

Now, it’s time to test your application. Follow these steps:

  1. Start the development server: If it’s not already running, start your Next.js development server using `npm run dev`.
  2. Open the application in your browser: Go to http://localhost:3000 in your web browser.
  3. Upload files: Click the “Choose Files” button and select the files you want to rename.
  4. Enter a renaming pattern: In the “Renaming Pattern” input field, enter a pattern (e.g., “image_[index].[extension]”).
  5. Review the preview: Check the “Preview” section to see how the files will be renamed.
  6. Rename the files: Click the “Rename Files” button.
  7. Download the renamed files: After the files are renamed, a “Download Renamed Files” link will appear. Click it to download a ZIP archive containing the renamed files.

Make sure the files are renamed correctly according to the pattern you specified. If you encounter any errors, check the browser’s console and the server-side logs for troubleshooting information.

Common Mistakes and How to Fix Them

1. CORS Errors

Problem: You might encounter CORS (Cross-Origin Resource Sharing) errors when making requests from your front-end to your API route. This happens because the browser blocks requests to a different origin (domain, protocol, or port) than the one where the web page originated.

Solution:

  • No need to fix: Because we are using Next.js, the front-end and back-end are served from the same origin (localhost:3000).

2. File Upload Errors

Problem: File upload might fail if the server-side code doesn’t handle the file uploads correctly, or if file size limits are exceeded.

Solution:

  • Check the server-side code: Ensure that the `formidable` package is correctly configured to handle file uploads.
  • Verify file paths: Double-check that the file paths are correctly specified in your server-side code.
  • Increase file size limits: You might need to increase the file size limits in your server-side code if the files are too large.

3. Incorrect Renaming Pattern

Problem: The files might not be renamed as expected if the renaming pattern is incorrect.

Solution:

  • Review the pattern: Carefully review the renaming pattern you entered, ensuring that the placeholders (`[index]`, `[name]`, and `[extension]`) are correctly placed.
  • Test different patterns: Experiment with different patterns to understand how they affect the renaming process.
  • Check the preview: Before renaming the files, always check the preview to ensure the renaming pattern will produce the desired results.

4. ZIP Creation Errors

Problem: The ZIP file creation might fail, leading to errors when the user tries to download the renamed files.

Solution:

  • Check the `jszip` code: Ensure that the `jszip` library is correctly used to create the ZIP archive.
  • Verify file paths: Double-check that the file paths used when adding files to the ZIP archive are correct.
  • Error handling: Implement proper error handling to catch any exceptions during the ZIP creation process.

Key Takeaways and Summary

You’ve successfully built a file renamer application using Next.js! Here’s a summary of what you’ve learned:

  • Next.js for File Renaming: Next.js is a great framework for this type of application, offering features like server-side rendering, easy routing, and API routes.
  • Front-End Development: You’ve learned how to create a user-friendly interface with file upload, renaming pattern input, and a preview.
  • Back-End Development: You’ve implemented an API route to handle file uploads, renaming, and ZIP file creation.
  • File Handling: You’ve gained experience with file upload using the `formidable` package, file system operations, and ZIP file creation.
  • Error Handling: You’ve implemented error handling to provide a better user experience.

Optional: FAQ

Here are some frequently asked questions about the file renamer application:

  1. Can I rename files other than images? Yes, you can rename any type of file that you can upload and download.
  2. Can I customize the download location? The download location is determined by the browser’s default download settings. You can’t customize this directly in the application.
  3. What happens to the original files? The original files are not modified or deleted. The application creates a ZIP archive with the renamed files, and the original files remain untouched.
  4. Can I add more renaming options? Yes, you can extend the application to include more renaming options, such as date-based renaming, find and replace, and more.

Building this file renamer app is a solid foundation for understanding web application development with Next.js. You can expand upon this project, adding more features or integrating it into other applications. The skills you’ve acquired—handling file uploads, working with API routes, and creating user interfaces—are valuable in many web development projects. Furthermore, by practicing error handling, you’ll be better equipped to handle real-world challenges in your future projects. The ability to efficiently manage files is crucial in numerous professional and personal contexts, making this app a practical and useful tool for anyone working with digital assets. Whether it’s organizing a photo library, preparing files for a presentation, or streamlining a project workflow, this file renamer app offers a valuable solution. The modular design of the application also allows for easy expansion. For instance, you could add the option to rename files on the server directly, without the need for a ZIP archive, or integrate with cloud storage services. The possibilities are truly endless, and this project provides a solid starting point for many exciting opportunities.