Build a Simple Next.js Interactive URL Shortener

Written by

in

In today’s fast-paced digital world, sharing long and unwieldy URLs can be a real pain. Whether you’re tweeting, messaging, or simply trying to make a link more manageable, long URLs can clutter your content and look unprofessional. This is where URL shorteners come in handy. They transform lengthy web addresses into concise, easy-to-share links. This tutorial will guide you through building your own interactive URL shortener using Next.js, a powerful React framework known for its server-side rendering and static site generation capabilities.

Why Build a URL Shortener?

Creating your own URL shortener offers several advantages:

  • Privacy: You have complete control over your data and the links you shorten.
  • Branding: You can use your own domain, enhancing brand recognition.
  • Customization: Tailor the functionality and features to your specific needs.
  • Learning: It’s a great project to learn about Next.js, API routes, and database interactions.

Prerequisites

Before we dive in, ensure you have the following:

  • Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn installed on your system to manage project dependencies.
  • Basic understanding of JavaScript and React: Familiarity with JavaScript and React concepts will be helpful.
  • Text editor or IDE: Choose your preferred code editor (VS Code, Sublime Text, etc.).

Project Setup

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

npx create-next-app url-shortener

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

cd url-shortener

Installing Dependencies

We’ll need a few dependencies for this project. Install them using npm or yarn:

npm install nanoid @vercel/kv-orm @vercel/kv-store

or

yarn add nanoid @vercel/kv-orm @vercel/kv-store
  • nanoid: For generating unique short link IDs.
  • @vercel/kv-store: For storing the key-value pairs.
  • @vercel/kv-orm: For interacting with the database.

Project Structure

Your project structure will look something like this:

url-shortener/
├── pages/
│   ├── api/
│   │   └── shorten.js
│   └── index.js
├── public/
│   └── ...
├── styles/
│   └── globals.css
├── .gitignore
├── next.config.js
├── package.json
└── README.md

Creating the API Route for Shortening URLs

Next.js makes it easy to create API routes. We’ll create an API endpoint to handle the URL shortening logic. Create a file named shorten.js inside the pages/api directory.

Here’s the code for pages/api/shorten.js:

import { nanoid } from 'nanoid';
import { createClient } from '@vercel/kv-store';
import { createOrm } from '@vercel/kv-orm';

const kv = createClient({
  url: process.env.KV_REST_API_URL,
  token: process.env.KV_REST_API_TOKEN,
});

const orm = createOrm(kv);

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

    if (!originalUrl) {
      return res.status(400).json({ error: 'Original URL is required' });
    }

    const shortId = nanoid(6);

    try {
      await orm.set(`url:${shortId}`, originalUrl);
      const shortUrl = `${req.headers.origin}/${shortId}`;
      return res.status(200).json({ shortUrl });
    } catch (error) {
      console.error(error);
      return res.status(500).json({ error: 'Failed to shorten URL' });
    }
  }

  return res.status(405).json({ error: 'Method Not Allowed' });
}

Let’s break down the code:

  • Import Statements: We import `nanoid` for generating unique IDs, and `createClient` and `createOrm` for interacting with the database.
  • KV Store Initialization: We initialize the KV store using environment variables. Make sure you set the KV_REST_API_URL and KV_REST_API_TOKEN in your .env file or Vercel environment variables. You can find this information in your Vercel project’s settings.
  • Request Handling: The `handler` function processes incoming requests. We check if the method is POST (to shorten a URL).
  • Input Validation: We validate that the `originalUrl` is provided in the request body.
  • Short ID Generation: We use `nanoid(6)` to generate a unique 6-character short ID.
  • Database Interaction: We store the original URL in the KV store using the short ID as the key.
  • Response: We construct the short URL (e.g., `https://yourdomain.com/shortId`) and return it in the response. If any error occurs, we return an error message.
  • Error Handling: We include a try…catch block to handle potential errors during the database interaction.

Creating the Home Page (index.js)

Now, let’s create the user interface in pages/index.js. This will be the main page where users can enter the long URL and get the shortened version.

import { useState } from 'react';
import styles from '../styles/Home.module.css';

export default function Home() {
  const [originalUrl, setOriginalUrl] = useState('');
  const [shortUrl, setShortUrl] = useState('');
  const [error, setError] = useState('');

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

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

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'Failed to shorten URL');
      }

      const data = await response.json();
      setShortUrl(data.shortUrl);
    } catch (error) {
      setError(error.message);
    }
  };

  return (
    <div>
      <main>
        <h1>URL Shortener</h1>

        
           setOriginalUrl(e.target.value)}
            placeholder="Enter your URL"
            className={styles.input}
            required
          />
          <button type="submit">Shorten</button>
          {error && <p>{error}</p>}
          {shortUrl && (
            <p>Shortened URL: <a href="{shortUrl}" target="_blank" rel="noopener noreferrer">{shortUrl}</a></p>
          )}
        

      </main>
    </div>
  );
}

Let’s break down this code:

  • Import Statements: We import `useState` from React for managing the component’s state and import styles from ../styles/Home.module.css.
  • State Variables: We declare state variables: `originalUrl` to store the URL entered by the user, `shortUrl` to display the shortened URL, and `error` to display any error messages.
  • handleSubmit Function: This function is called when the form is submitted. It prevents the default form submission behavior, clears any existing errors or short URLs, and then sends a POST request to the /api/shorten endpoint with the original URL.
  • API Call: The fetch function is used to make the API call to the backend.
  • Response Handling: If the response is successful (status code 200), the function parses the JSON response and updates the shortUrl state with the shortened URL. If there is an error, it updates the error state.
  • JSX: The JSX renders a simple form with an input field for the original URL, a submit button, and displays the shortened URL or any error messages. The short URL is displayed as a clickable link.

Styling the Application

To style the application, open styles/Home.module.css and add the following CSS:

.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: #f0f0f0;
}

.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 80%;
  max-width: 600px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
}

.title {
  margin: 0;
  line-height: 1.15;
  font-size: 3rem;
  text-align: center;
  margin-bottom: 2rem;
  color: #333;
}

.form {
  display: flex;
  flex-direction: column;
  width: 80%;
  margin-bottom: 2rem;
}

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

.button {
  padding: 1rem 2rem;
  background-color: #0070f3;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.button:hover {
  background-color: #005fdb;
}

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

.shortUrl {
  margin-top: 1rem;
  text-align: center;
}

@media (prefers-color-scheme: dark) {
  .container {
    background-color: #121212;
  }

  .main {
    background-color: #222;
  }

  .title {
    color: #eee;
  }

  .input, .button {
    color: #eee;
    background-color: #333;
    border-color: #555;
  }
}

This CSS provides basic styling for the layout, form elements, and error messages. You can customize this to fit your design preferences.

Setting Up Environment Variables

To run the application, you need to set up environment variables for your KV store. If you’re deploying on Vercel, you can set these directly in your Vercel project’s settings. If you’re running locally, create a .env.local file in your project root and add the following:

KV_REST_API_URL=<your_kv_rest_api_url>
KV_REST_API_TOKEN=<your_kv_rest_api_token>

Replace <your_kv_rest_api_url> and <your_kv_rest_api_token> with your actual KV store credentials.

Testing the Application

To run the application locally, use the following command:

npm run dev

or

yarn dev

This will start the development server. Open your browser and go to http://localhost:3000. Enter a long URL into the input field and click the “Shorten” button. You should see the shortened URL displayed below the form. You can click on the shortened URL to test if the redirect works.

Implementing the Redirect

The final step is to implement the redirect. This is done in the pages/[id].js file. Create this file in your pages directory. This file will handle requests to the short URLs (e.g., /<shortId>) and redirect to the original URL.

import { createClient } from '@vercel/kv-store';
import { createOrm } from '@vercel/kv-orm';

const kv = createClient({
  url: process.env.KV_REST_API_URL,
  token: process.env.KV_REST_API_TOKEN,
});

const orm = createOrm(kv);

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

  try {
    const originalUrl = await orm.get(`url:${id}`);

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

    return {
      props: { originalUrl },
    };
  } catch (error) {
    console.error(error);
    return {
      notFound: true,
    };
  }
}

export default function Redirect({ originalUrl }) {
  if (originalUrl) {
    return (
      <div>
        {/* Redirecting... */}
        
      </div>
    );
  } else {
    return <div>URL not found.</div>;
  }
}

Let’s break down this code:

  • Import Statements: We import createClient and createOrm for interacting with the database.
  • KV Store Initialization: We initialize the KV store using environment variables.
  • getServerSideProps: This function is a Next.js function that runs on the server-side. It fetches the original URL from the database based on the short ID in the URL path (e.g., /<shortId>).
  • Context: The context object contains information about the request, including the URL parameters. We extract the id (the short ID) from context.query.
  • Database Lookup: We use orm.get(`url:${id}`) to retrieve the original URL from the KV store using the short ID as the key.
  • Error Handling: If the original URL is not found, it returns a 404 Not Found error. If any other error occurs, it also returns a 404.
  • Props: If the original URL is found, it’s passed as a prop to the component.
  • Redirect Component: The Redirect component receives the originalUrl as a prop. It uses a meta tag with httpEquiv="refresh" to redirect the user to the original URL. If the original URL is not found, it displays a “URL not found” message.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Incorrect Environment Variables: Make sure your KV_REST_API_URL and KV_REST_API_TOKEN are correctly set in your .env.local file or Vercel environment variables. Double-check the values and ensure there are no typos.
  • CORS Errors: If you encounter CORS (Cross-Origin Resource Sharing) errors, ensure your API route is configured to allow requests from your frontend’s origin. This is usually handled automatically by Next.js, but can sometimes cause issues.
  • Incorrect URL: Make sure you are entering the correct URL in the input field. It should start with http:// or https://.
  • Database Connection Issues: If you are unable to connect to your KV store, verify your network connection and KV store credentials. Check the Vercel dashboard or your KV store provider’s documentation for troubleshooting tips.
  • Missing Dependencies: Make sure you have installed all the necessary dependencies using npm install or yarn add.

Key Takeaways

  • Next.js API Routes: Next.js API routes provide a simple way to create backend endpoints.
  • KV Store: Vercel KV is a fast and reliable key-value store for storing data.
  • Server-Side Rendering: getServerSideProps allows you to fetch data on the server-side, which is crucial for URL redirection.
  • Error Handling: Always include error handling to gracefully handle unexpected situations.

FAQ

Q: Can I use a different database?

A: Yes, you can use any database that works with Next.js. You would need to modify the code to interact with your chosen database (e.g., MongoDB, PostgreSQL, etc.).

Q: How do I deploy this application?

A: Deploying a Next.js application is very straightforward. You can deploy it on Vercel (recommended), Netlify, or other hosting platforms. Make sure you set your environment variables during the deployment process.

Q: How can I add analytics to track link clicks?

A: You can add analytics by tracking clicks on the shortened URL. You can store the click counts in your database, and then display the analytics on a dashboard. You can use a service like Google Analytics or Vercel Analytics. You would need to modify the redirect page to log the click, and potentially add an admin interface to view the statistics.

Q: Can I use a custom domain?

A: Yes, you can use a custom domain. You will need to configure your domain’s DNS settings to point to your hosting platform (e.g., Vercel). This usually involves setting up a CNAME record or A record.

Building your own URL shortener with Next.js is a rewarding project that combines front-end and back-end development. You’ve learned how to create API routes, interact with a database, and implement server-side redirects. By following this tutorial, you’ve gained practical experience with Next.js and Vercel KV, and you’re now equipped to create your own URL shortener, customize its features, and even integrate it into your own projects. Remember to consider security best practices, such as input validation and rate limiting, if you deploy your URL shortener publicly to prevent abuse. This project serves as a solid foundation for further exploration into web development and serverless technologies, opening doors to more sophisticated and personalized web applications.