Build a Simple Next.js Interactive Web-Based Recipe Finder

Written by

in

Tired of endlessly scrolling through recipe websites, only to be bombarded with ads and irrelevant content? Wouldn’t it be great to have a simple, clean, and fast way to search for recipes based on ingredients you have on hand? In this tutorial, we’ll build a fully functional, interactive recipe finder using Next.js, a powerful React framework that makes web development a breeze. This project is perfect for beginners and intermediate developers looking to hone their skills in Next.js, learn about API integrations, and create a user-friendly web application. We’ll focus on the core concepts, keeping the code clean, and the learning experience engaging.

Why Build a Recipe Finder?

Recipe finders are incredibly useful. They solve a common problem: the struggle of deciding what to cook, especially when you have specific ingredients you want to use up. This project allows you to:

  • Learn practical Next.js skills.
  • Understand how to fetch and display data from external APIs.
  • Practice building interactive user interfaces.
  • Create something useful that you can actually use.

Plus, it’s a fun and rewarding project to add to your portfolio!

Prerequisites

Before we dive in, make sure you have the following:

  • Node.js and npm (or yarn) installed on your machine.
  • A basic understanding of HTML, CSS, and JavaScript.
  • Familiarity with React (though not strictly required).
  • A code editor (like VS Code).

Setting Up the 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 recipe-finder

This command sets up a new Next.js project named “recipe-finder”. Navigate into the project directory:

cd recipe-finder

Now, let’s install some dependencies we’ll need. We’ll use Axios to make API requests and possibly a CSS framework like Tailwind CSS for styling (optional, but recommended for faster development):

npm install axios tailwindcss postcss autoprefixer

If you choose to use Tailwind CSS, initialize it in your project:

npx tailwindcss init -p

Configure Tailwind CSS in your `tailwind.config.js` file. Add the following to the `content` array:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}',
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Include Tailwind directives in your `globals.css` file (located in the `styles` directory):

@tailwind base;
@tailwind components;
@tailwind utilities;

Fetching Recipe Data from an API

We’ll use a free recipe API. There are several options available; for this tutorial, we will utilize the Spoonacular API, but you can choose any API that offers a free tier. You’ll need to sign up for a free API key. Once you have your API key, store it securely (e.g., in a `.env` file) to prevent it from being exposed in your code. Create a `.env.local` file in your project root and add the following:

SPOONACULAR_API_KEY=YOUR_API_KEY

Replace `YOUR_API_KEY` with your actual API key.

Now, let’s create a function to fetch recipes. Create a new file called `api.js` inside a `utils` folder (create this folder if you don’t have it already) in your project’s root directory. This file will contain our API-related functions.

// utils/api.js
import axios from 'axios';

const API_BASE_URL = 'https://api.spoonacular.com/recipes';
const API_KEY = process.env.SPOONACULAR_API_KEY;

export async function searchRecipes(query) {
  try {
    const response = await axios.get(`${API_BASE_URL}/complexSearch`, {
      params: {
        apiKey: API_KEY,
        query: query,
        number: 10, // Number of results to return
      },
    });
    return response.data.results;
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

In this code:

  • We import `axios` for making HTTP requests.
  • We define the API base URL and retrieve the API key from our `.env.local` file using `process.env.SPOONACULAR_API_KEY`. Important: Environment variables are only available on the server-side in Next.js unless you prefix them with `NEXT_PUBLIC_`. We don’t need to do that here because we’re not exposing the key in the client-side code.
  • The `searchRecipes` function takes a `query` (the search term) as input.
  • It uses `axios.get` to make a request to the Spoonacular API’s `/complexSearch` endpoint. We pass the `apiKey`, `query`, and the number of results to return as parameters.
  • It returns the `results` from the API response.
  • Error handling is included to catch and log any API errors.

Building the Recipe Search Component

Now, let’s create a component to handle the recipe search functionality. Create a new file called `RecipeSearch.js` in a `components` folder (create this if you don’t have it):

// components/RecipeSearch.js
import { useState } from 'react';
import { searchRecipes } from '../utils/api';

function RecipeSearch() {
  const [query, setQuery] = useState('');
  const [recipes, setRecipes] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSearch = async () => {
    setLoading(true);
    setError(null);
    try {
      const results = await searchRecipes(query);
      setRecipes(results);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="container mx-auto p-4">
      <h2 className="text-2xl font-bold mb-4">Recipe Finder</h2>
      <div className="flex items-center mb-4">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Search for recipes..."
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        />
        <button
          onClick={handleSearch}
          className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-2"
          disabled={loading}
        >
          {loading ? 'Searching...' : 'Search'}
        </button>
      </div>
      {error && <p className="text-red-500">Error: {error.message}</p>}
      {recipes.length > 0 && (
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
          {recipes.map((recipe) => (
            <div key={recipe.id} className="rounded overflow-hidden shadow-lg">
              <img
                src={recipe.image}
                alt={recipe.title}
                className="w-full h-48 object-cover"
              />
              <div className="px-6 py-4">
                <div className="font-bold text-xl mb-2">{recipe.title}</div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default RecipeSearch;

Let’s break down this component:

  • We import `useState` from React and the `searchRecipes` function from `../utils/api`.
  • We define state variables: `query` (the search input value), `recipes` (the array of recipes fetched from the API), `loading` (a boolean to indicate if the search is in progress), and `error` (to display any errors).
  • `handleSearch` is an `async` function that is triggered when the search button is clicked. It sets `loading` to `true`, clears any previous errors, calls the `searchRecipes` function with the current `query`, updates the `recipes` state with the results, and sets `loading` to `false` in a `finally` block (to ensure it always runs, even if there’s an error).
  • The component renders a search input field, a search button (which is disabled while loading), and a conditional display of the recipes. It also displays an error message if there is an error.
  • The recipes are mapped to display their title and image. The images are displayed using the `recipe.image` URL returned by the API.

Integrating the Recipe Search Component in the Page

Now, let’s integrate our `RecipeSearch` component into the main page of our Next.js application. Open `pages/index.js` (or `app/page.js` if you are using the app router) and modify it as follows:

// pages/index.js or app/page.js
import RecipeSearch from '../components/RecipeSearch';

export default function Home() {
  return (
    <div className="bg-gray-100 min-h-screen">
      <RecipeSearch />
    </div>
  );
}

This imports the `RecipeSearch` component and renders it within a basic layout. We’ve added some basic styling using Tailwind CSS to provide a background color.

Running the Application

To run your application, open your terminal, navigate to your project directory, and run:

npm run dev

or

yarn dev

This will start the development server, and you can view your application in your web browser at `http://localhost:3000`. You should see the search input and the search button. Enter a search term and click “Search” to see the results. If everything is set up correctly, you should see the recipe titles and images.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • API Key Issues: Double-check that your API key is correctly stored in your `.env.local` file and that you’re referencing it correctly in your `api.js` file (using `process.env.SPOONACULAR_API_KEY`). Also, make sure that your API key has not expired.
  • CORS Errors: If you encounter CORS (Cross-Origin Resource Sharing) errors, it means your browser is blocking the API request. This can happen if the API doesn’t allow requests from your origin (localhost:3000). Some APIs might require you to specify your allowed origins in their settings. For local development, you might be able to use a CORS proxy (but this isn’t recommended for production).
  • Incorrect API Endpoint: Ensure you are using the correct API endpoint and that the parameters you are passing are correct (e.g., `query`, `apiKey`, etc.). Refer to the API documentation.
  • Typos: Carefully review your code for typos, especially in variable names and API endpoint URLs.
  • Missing Dependencies: Verify that you have installed all necessary dependencies (e.g., `axios`, `tailwindcss`).
  • Server-Side vs. Client-Side Errors: Errors during the API request can happen on the server-side (in `api.js`) or client-side (in `RecipeSearch.js`). Check the console in both your terminal (for server-side errors) and your browser’s developer tools (for client-side errors) to identify the source of the problem.
  • Image Display Issues: Make sure the `recipe.image` URL returned by the API is correct and accessible. If the images aren’t displaying, check the console for any network errors related to image loading.

Enhancements and Next Steps

This is a basic recipe finder. Here are some ideas to enhance it:

  • Add Pagination: Implement pagination to handle a large number of search results. The API likely provides a way to specify the `offset` or `page` for the results.
  • Implement Ingredient Search: Allow users to search by ingredients. The API might offer an “ingredients” parameter to refine the search.
  • Display Recipe Details: When a user clicks on a recipe, fetch and display more detailed information about the recipe (ingredients, instructions, etc.). You’ll need to use another API endpoint (e.g., `/recipes/{id}/information`).
  • Add Filtering: Allow users to filter recipes by dietary restrictions (e.g., vegetarian, vegan, gluten-free).
  • Improve UI/UX: Enhance the user interface with better styling, loading indicators, and error messages. Consider using a UI component library (like Material UI or Ant Design) to speed up the development of the UI.
  • Implement Caching: Cache the API responses to reduce API calls and improve performance. Next.js provides built-in mechanisms for caching.
  • Add User Authentication: Allow users to save their favorite recipes or create custom meal plans (this would require adding user authentication).
  • Deploy your app: Deploy your Next.js app to a platform like Vercel or Netlify to make it accessible to everyone.

Summary / Key Takeaways

In this tutorial, we’ve successfully built a basic, yet functional, recipe finder application using Next.js. We’ve covered the essential steps: setting up a Next.js project, fetching data from an external API (Spoonacular), creating a user interface with React components, and handling user input. You’ve learned how to integrate an API, manage state, and build a dynamic web application. This project demonstrates how Next.js can be used to create interactive, data-driven web applications with ease. Remember to always prioritize clean code, error handling, and a good user experience. The skills you’ve gained here are transferable to many other web development projects. Experiment, iterate, and enjoy the process of building!

FAQ

Q: Where can I find a free recipe API?
A: There are several free recipe APIs available. We used Spoonacular in this tutorial, but others include Edamam and Recipe Puppy. Be sure to check the API’s terms of service and usage limits.

Q: How do I handle API keys securely?
A: The best practice is to store API keys in environment variables (e.g., in a `.env.local` file) and never commit them to your code repository. In Next.js, you can access environment variables using `process.env.YOUR_VARIABLE_NAME`. Remember that environment variables prefixed with `NEXT_PUBLIC_` are exposed to the client-side.

Q: How do I deploy my Next.js application?
A: You can deploy your Next.js application to platforms like Vercel (which is built by the creators of Next.js), Netlify, or other hosting providers. These platforms often provide one-click deployment options and handle the server-side rendering and build process for you.

Q: What is the difference between the “pages” and “app” directories in Next.js?
A: Next.js offers two routing systems: the `pages` directory (older) and the `app` directory (newer). The `pages` directory uses file-system-based routing, where each file in the `pages` directory becomes a route. The `app` directory uses a more flexible, component-based routing system with features like layouts, nested routes, and server components. The `app` directory is the recommended approach for new projects.

As you continue to build and refine this recipe finder, consider how you can personalize it to your own needs. Perhaps you’ll add features to save favorite recipes or integrate with other services. The journey of building a web application is a continuous learning process, and each project is an opportunity to expand your knowledge and skills. Keep exploring, keep coding, and keep creating!