Build a Simple Next.js Interactive URL Shortener

Written by

in

In today’s digital landscape, links are everywhere. From social media posts to email campaigns, concise and shareable links are crucial. Long, unwieldy URLs can be off-putting and difficult to manage. This is where URL shorteners come in, transforming lengthy addresses into manageable, easily shareable ones. This article guides you through building your own interactive URL shortener using Next.js, a powerful React framework for building web applications. We’ll cover everything from the basics of Next.js to implementing the core functionality of shortening and redirecting URLs. By the end, you’ll have a functional URL shortener and a solid understanding of Next.js fundamentals.

Why Build a URL Shortener?

Creating your own URL shortener offers several advantages:

  • Control: You have complete control over your shortened links and the associated data.
  • Branding: Use your own domain name to enhance brand recognition.
  • Analytics: Track clicks and gain insights into user behavior.
  • Learning: It’s a fantastic project to learn and practice web development skills, especially with Next.js.

Prerequisites

Before we dive in, ensure you have the following:

  • Node.js and npm (or yarn): These are essential for managing project dependencies and running the development server.
  • A Code Editor: Visual Studio Code, Sublime Text, or any editor you prefer.
  • Basic Understanding of JavaScript and React: Familiarity with these technologies will be helpful, but we’ll explain the Next.js specific concepts.
  • A Domain Name (Optional): While not mandatory for this project, having a domain name allows you to personalize your URL shortener.

Setting Up Your Next.js Project

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

npx create-next-app url-shortener
cd url-shortener

This command creates a new Next.js project named “url-shortener”. Navigate into the project directory using `cd url-shortener`.

Project Structure Overview

Next.js projects have a specific file structure. Here’s a brief overview:

  • `pages/`: This directory contains your application’s pages. Each file in this directory represents a route in your application. For example, `pages/index.js` will be accessible at the root path (`/`).
  • `components/`: This directory is where you’ll store reusable React components.
  • `public/`: This directory holds static assets like images, fonts, and other files.
  • `styles/`: This directory is for your CSS or styling files.
  • `package.json`: This file lists your project’s dependencies and scripts.

Building the Frontend (User Interface)

Let’s design the user interface for our URL shortener. We’ll create a simple form where users can enter a long URL and receive a shortened one.

Open `pages/index.js` and replace the existing code with the following:

import { useState } from 'react';

export default function Home() {
  const [longUrl, setLongUrl] = useState('');
  const [shortUrl, setShortUrl] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    setShortUrl('');
    setLoading(true);

    try {
      const response = await fetch('/api/shorten', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ longUrl }),
      });

      const data = await response.json();

      if (response.ok) {
        setShortUrl(data.shortUrl);
      } else {
        setError(data.message || 'An error occurred.');
      }
    } catch (err) {
      setError('An unexpected error occurred.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="container">
      <h1>URL Shortener</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor="longUrl">Enter URL:</label>
        <input
          type="url"
          id="longUrl"
          value={longUrl}
          onChange={(e) => setLongUrl(e.target.value)}
          required
        />
        <button type="submit" disabled={loading}>{loading ? 'Shortening...' : 'Shorten'}</button>
      </form>
      {error && <p className="error">{error}</p>}
      {shortUrl && (
        <div className="short-url-container">
          <p>Shortened URL: <a href={shortUrl} target="_blank" rel="noopener noreferrer">{shortUrl}</a></p>
          <button onClick={() => navigator.clipboard.writeText(shortUrl)}>Copy</button>
        </div>
      )}
    </div>
  );
}

This code does the following:

  • Imports the `useState` hook from React to manage the form’s state (long URL, short URL, error messages, and loading state).
  • Defines a `handleSubmit` function that is triggered when the form is submitted. This function makes a POST request to the `/api/shorten` endpoint (which we’ll create later) to shorten the URL.
  • Displays the shortened URL and provides a copy button if the shortening is successful.
  • Handles error messages and displays them to the user.
  • Uses a loading state to disable the submit button while the request is in progress.

Now, let’s add some basic styling to make the UI look better. Create a file named `styles/Home.module.css` and add the following CSS:


.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2rem;
  font-family: sans-serif;
}

h1 {
  margin-bottom: 1rem;
}

form {
  display: flex;
  flex-direction: column;
  width: 300px;
}

label {
  margin-bottom: 0.5rem;
}

input {
  padding: 0.5rem;
  margin-bottom: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  padding: 0.75rem 1rem;
  background-color: #0070f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #0056b3;
}

.error {
  color: red;
  margin-top: 1rem;
}

.short-url-container {
  margin-top: 1rem;
  text-align: center;
}

Import this CSS file into `pages/index.js` by adding the following line at the top of the file:

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

And then apply the styles to the main `div` element in your `return` statement:

<div className={styles.container}>
  {/* ... rest of the code ... */}
</div>

Run your development server using `npm run dev` or `yarn dev`. You should now see the basic UI in your browser at `http://localhost:3000/`.

Building the Backend (API Route)

Next.js allows you to create API routes within your project. We’ll use this to handle the URL shortening logic. Create a new file at `pages/api/shorten.js` and add the following code:

import { nanoid } from 'nanoid';

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; // Make sure this is your app's URL

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { longUrl } = req.body;

    if (!longUrl) {
      return res.status(400).json({ message: 'URL is required' });
    }

    try {
      const shortCode = nanoid(6); // Generate a unique short code
      const shortUrl = `${BASE_URL}/${shortCode}`;

      // In a real application, you would save the long URL and short code to a database.
      // For this example, we'll use a simple in-memory object (not persistent).
      const urlMap = {
        [shortCode]: longUrl,
      };

      // Simulate storing the data (replace with database interaction in a real project)
      // In this example, we're not actually storing the data persistently.
      // In a real application, you'd use a database like MongoDB, PostgreSQL, etc.

      return res.status(200).json({ shortUrl });
    } catch (error) {
      console.error('Error shortening URL:', error);
      return res.status(500).json({ message: 'Failed to shorten URL' });
    }
  } else {
    res.setHeader('Allow', ['POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

This code does the following:

  • Imports `nanoid` to generate unique short codes. You’ll need to install this package: `npm install nanoid`.
  • Defines a `handler` function that processes the request.
  • Checks if the request method is POST.
  • Extracts the `longUrl` from the request body.
  • Generates a unique short code using `nanoid(6)` (6 characters long).
  • Constructs the `shortUrl`.
  • Important: In a real-world application, you would store the `longUrl` and the `shortCode` in a database (e.g., MongoDB, PostgreSQL, etc.). The provided code includes comments to guide you on this. For this example, it uses an in-memory object, which is not persistent and will lose data when the server restarts.
  • Returns the `shortUrl` in the response.
  • Handles errors and returns appropriate status codes.

To make the `BASE_URL` configurable, create a `.env.local` file in the root of your project and add the following line (replace with your actual domain if you have one):

NEXT_PUBLIC_BASE_URL=http://localhost:3000

This allows you to easily change the base URL without modifying the code directly. The `NEXT_PUBLIC_` prefix makes the environment variable accessible in the browser.

Implementing the Redirect

Now, let’s implement the redirect functionality. This is the core of a URL shortener: when a user visits the short URL, they should be redirected to the original long URL. Create a new file at `pages/[shortCode].js` and add the following code:


import { useRouter } from 'next/router';

const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';

export async function getServerSideProps(context) {
  const { shortCode } = context.query;

  // In a real application, fetch from your database.
  // This is a placeholder for your database interaction.
  // Replace this with your database query.
  const urlMap = {
    // Example entries (replace with your database)
    // 'abcdef': 'https://www.example.com'
  };

  const longUrl = urlMap[shortCode];

  if (!longUrl) {
    return {
      notFound: true,
    };
  }

  return {
    props: { longUrl },
  };
}

export default function Redirect({ longUrl }) {
  const router = useRouter();

  // Redirect immediately on the client-side.
  // Consider using a meta refresh tag for SEO, too.
  router.replace(longUrl);

  return null; // Or a loading indicator.
}

This code does the following:

  • Uses `getServerSideProps` to fetch the long URL based on the `shortCode` from the URL. `getServerSideProps` runs on the server-side, which is essential for redirecting users correctly.
  • Important: This example uses a placeholder `urlMap`. You **must** replace this with code that fetches the long URL from your database based on the `shortCode`.
  • If the `shortCode` is not found in your database, it returns a 404 error.
  • Uses the `useRouter` hook from `next/router` to perform the redirect on the client-side. The `router.replace()` method ensures the user doesn’t have a back button history entry for the short URL.

Testing Your URL Shortener

Now, let’s test your URL shortener:

  1. Run your development server (`npm run dev` or `yarn dev`).
  2. Open your browser and go to `http://localhost:3000/`.
  3. Enter a long URL in the input field and click “Shorten”.
  4. You should see a shortened URL displayed.
  5. Click on the shortened URL. You should be redirected to the original long URL. (If you haven’t implemented database storage, this may not work correctly as the data will not persist).

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Not Using a Database: The biggest mistake is not using a database to store the `longUrl` and `shortCode`. This means your shortened URLs will only work for the duration of the server’s runtime. Solution: Implement database integration using a service like MongoDB, PostgreSQL, or a similar database.
  • Incorrect Base URL: Make sure your `BASE_URL` (or `NEXT_PUBLIC_BASE_URL`) is correctly configured in your `.env.local` file. This is crucial for generating the correct shortened URLs. Solution: Double-check your `.env.local` file and ensure the URL matches where your application is running.
  • Error Handling: Robust error handling is essential. Don’t just catch errors; provide informative error messages to the user. Solution: Implement try-catch blocks in your API route and frontend to handle potential errors (e.g., network errors, database connection errors). Display user-friendly error messages.
  • Security Considerations: Be mindful of security. Don’t allow users to shorten URLs that could be malicious. Solution: Implement input validation and potentially use a service like Google Safe Browsing API to check URLs for safety. Consider rate limiting to prevent abuse.
  • SEO Considerations: Consider SEO implications. While client-side redirects are fine, server-side redirects (using `getServerSideProps`) are generally preferred for SEO. Solution: Use `getServerSideProps` for the redirect functionality, as shown in the example code. Consider adding a meta refresh tag for additional SEO benefits.
  • Missing Dependencies: Make sure to install all the necessary dependencies. Solution: Run `npm install nanoid` (or `yarn add nanoid`) if you haven’t already.

Advanced Features (Optional)

Once you have the basic functionality working, you can add more advanced features:

  • Custom Short Codes: Allow users to specify their own short codes (e.g., `/my-custom-code`). This requires extra logic to check for code availability.
  • Analytics: Track clicks on your shortened URLs. Store the number of clicks, the date, and potentially the user’s location and browser information.
  • User Accounts: Allow users to create accounts to manage their shortened URLs.
  • QR Code Generation: Generate QR codes for the shortened URLs.
  • API: Create an API endpoint to allow other applications to use your URL shortener.

Key Takeaways

  • Next.js provides a streamlined way to build web applications, including API routes and server-side rendering.
  • You can create interactive web applications with React components and handle user input with hooks like `useState`.
  • Understanding the difference between client-side and server-side rendering is crucial for SEO and redirect functionality.
  • Always prioritize data storage using a database for persistent data.

Building a URL shortener is a great way to learn about Next.js and web development fundamentals. Remember to always prioritize security, error handling, and a good user experience. This project offers a solid foundation upon which you can build more complex web applications. By understanding the core concepts of Next.js, API routes, and database integration, you can create a powerful and useful tool. As you expand the features, consider the user experience, making the interface intuitive and the process seamless. The more you explore, the more you’ll uncover the power and flexibility of Next.js, turning a simple project into a valuable learning experience.