In today’s digital world, having quick and easy access to recipes is a must. Whether you’re a seasoned chef or a kitchen novice, a well-designed recipe application can be a game-changer. Imagine being able to search for recipes, view ingredients, and follow step-by-step instructions, all within a sleek and responsive web application. This tutorial will guide you through building precisely that: a simple, yet functional, interactive web-based recipe app using Next.js.
Why Next.js?
Next.js is a powerful React framework that offers several advantages for building web applications. It provides features like server-side rendering (SSR), static site generation (SSG), and optimized image handling, leading to improved performance and SEO. Moreover, Next.js simplifies the development process with its intuitive routing and built-in features, making it an excellent choice for this project.
What You’ll Learn
By the end of this tutorial, you’ll have a fully functional recipe app and learn the following:
- Setting up a Next.js project.
- Creating components for displaying recipes and their details.
- Fetching data from a simple API (we’ll create one).
- Implementing search functionality.
- Styling the application using CSS modules.
- Deploying your app for the world to see.
Prerequisites
Before we begin, make sure you have the following installed on your system:
- Node.js (version 14 or higher)
- npm or yarn (package manager)
- A code editor (e.g., VS Code, Sublime Text)
Step 1: Setting Up the Next.js Project
Let’s start by creating a new Next.js project. Open your terminal and run the following command:
npx create-next-app recipe-app
This command will create a new directory called recipe-app and initialize a Next.js project inside it. Navigate into the project directory:
cd recipe-app
Now, 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. This confirms that your project is set up correctly.
Step 2: Project Structure and Component Creation
Let’s organize our project. We’ll create a few key components:
components/RecipeCard.js: Displays a summary of a single recipe.components/RecipeDetail.js: Displays the detailed information of a recipe.pages/index.js: The main page, displaying the recipe search and listing results.pages/recipe/[id].js: Displays the detailed information for a specific recipe (dynamic route).
Create the components directory inside your project’s root directory. Then, create the RecipeCard.js file inside the components directory. Add the following code:
// components/RecipeCard.js
import Link from 'next/link';
const RecipeCard = ({ recipe }) => {
return (
<div className="recipe-card">
<Link href={`/recipe/${recipe.id}`}>
<a>
<img src={recipe.image} alt={recipe.title} />
<h3>{recipe.title}</h3>
<p>{recipe.description}</p>
</a>
</Link>
<style jsx>{`
.recipe-card {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
text-decoration: none;
color: #333;
}
.recipe-card img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
margin-bottom: 8px;
}
.recipe-card h3 {
font-size: 1.2rem;
margin-bottom: 8px;
}
.recipe-card:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}</style>
</div>
);
};
export default RecipeCard;
This component displays a recipe’s image, title, and a brief description. It also uses a Next.js Link component to navigate to the recipe details page. We’ve also included basic styling using inline styles (a simple way to get started). Create a corresponding CSS Module file (RecipeCard.module.css) later for more advanced styling. The Link component is crucial for client-side navigation within your Next.js application, making transitions smoother and faster.
Next, create RecipeDetail.js inside the components directory:
// components/RecipeDetail.js
const RecipeDetail = ({ recipe }) => {
if (!recipe) {
return <p>Recipe not found.</p>;
}
return (
<div className="recipe-detail">
<img src={recipe.image} alt={recipe.title} />
<h2>{recipe.title}</h2>
<p>{recipe.description}</p>
<h3>Ingredients:</h3>
<ul>
{recipe.ingredients.map((ingredient, index) => (
<li key={index}>{ingredient}</li>
))}
</ul>
<h3>Instructions:</h3>
<ol>
{recipe.instructions.map((step, index) => (
<li key={index}>{step}</li>
))}
</ol>
<style jsx>{`
.recipe-detail {
padding: 20px;
}
.recipe-detail img {
width: 100%;
max-height: 400px;
object-fit: cover;
margin-bottom: 20px;
}
.recipe-detail h2 {
font-size: 1.8rem;
margin-bottom: 10px;
}
.recipe-detail h3 {
font-size: 1.4rem;
margin-top: 15px;
margin-bottom: 5px;
}
.recipe-detail ul, .recipe-detail ol {
margin-left: 20px;
}
`}</style>
</div>
);
};
export default RecipeDetail;
This component will display the full details of a recipe, including ingredients and instructions. It also includes basic styling using inline styles.
Step 3: Creating a Simple API (Mock Data)
For this tutorial, we’ll create a simple API using a JavaScript file to store our recipe data. In a real-world application, you would typically fetch this data from a database or a third-party API. Create a file named data.js in the root directory of your project and add the following code:
// data.js
const recipes = [
{
id: "1",
title: "Spaghetti Carbonara",
description: "Classic Italian pasta dish.",
image: "/images/carbonara.jpg",
ingredients: [
"Spaghetti",
"Eggs",
"Pancetta",
"Parmesan cheese",
"Black pepper",
],
instructions: [
"Cook spaghetti according to package directions.",
"Fry pancetta until crispy.",
"Whisk eggs, cheese, and pepper.",
"Toss spaghetti with pancetta and egg mixture.",
"Serve immediately.",
],
},
{
id: "2",
title: "Chicken Stir-Fry",
description: "Quick and easy chicken stir-fry.",
image: "/images/stir-fry.jpg",
ingredients: [
"Chicken breast",
"Broccoli",
"Soy sauce",
"Ginger",
"Garlic",
],
instructions: [
"Cut chicken into bite-sized pieces.",
"Stir-fry chicken with vegetables.",
"Add soy sauce and ginger.",
"Serve over rice.",
],
},
// Add more recipes here...
];
export function getRecipes() {
return recipes;
}
export function getRecipe(id) {
return recipes.find((recipe) => recipe.id === id);
}
This file contains an array of recipe objects and two functions: getRecipes() to retrieve all recipes and getRecipe(id) to retrieve a single recipe by its ID. Make sure you create the image files (e.g., carbonara.jpg and stir-fry.jpg) in a folder named public/images in your project’s root. Next.js automatically serves files in the public directory.
Step 4: Building the Main Page (pages/index.js)
Now, let’s build the main page of our application, which will display the recipe search and the list of recipes. Open pages/index.js and replace the default content with the following:
// pages/index.js
import { useState } from 'react';
import { getRecipes } from '../data';
import RecipeCard from '../components/RecipeCard';
const Home = () => {
const [searchQuery, setSearchQuery] = useState('');
const [recipes, setRecipes] = useState(getRecipes());
const handleSearch = (e) => {
const query = e.target.value.toLowerCase();
setSearchQuery(query);
const filteredRecipes = getRecipes().filter((recipe) =>
recipe.title.toLowerCase().includes(query)
);
setRecipes(filteredRecipes);
};
return (
<div className="container">
<h1>Recipe App</h1>
<input
type="text"
placeholder="Search recipes..."
value={searchQuery}
onChange={handleSearch}
/>
<div className="recipe-grid">
{recipes.map((recipe) => (
<RecipeCard key={recipe.id} recipe={recipe} />
))}
</div>
<style jsx>{`
.container {
padding: 20px;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 4px;
}
.recipe-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
`}</style>
</div>
);
};
export default Home;
This code imports the getRecipes function and the RecipeCard component. It then renders a search input field and a grid of recipe cards. The handleSearch function filters the recipes based on the search query. We’ve added basic styling using inline styles. Consider using CSS Modules or a CSS-in-JS solution for more maintainable styling in larger projects. The key prop in the map function is crucial for React to efficiently update the list when recipes change.
Step 5: Creating the Recipe Detail Page (pages/recipe/[id].js)
Next, we’ll create a dynamic route to display the details of a specific recipe. This will use the [id] syntax for dynamic routing in Next.js. Create a file named [id].js inside the pages/recipe directory (create the recipe directory if it doesn’t exist):
// pages/recipe/[id].js
import { useRouter } from 'next/router';
import { getRecipe } from '../../data';
import RecipeDetail from '../../components/RecipeDetail';
const RecipeDetailPage = () => {
const router = useRouter();
const { id } = router.query;
const recipe = getRecipe(id);
return <RecipeDetail recipe={recipe} />;
};
export default RecipeDetailPage;
This code uses the useRouter hook to access the route parameters. It retrieves the recipe ID from the URL and then fetches the recipe data using the getRecipe function. It then passes the recipe data to the RecipeDetail component. The router.query object contains the dynamic route parameters. Error handling (e.g., displaying a “Recipe not found” message) is included in the RecipeDetail component for robustness.
Step 6: Styling with CSS Modules (Optional but Recommended)
While the inline styles we’ve used so far are fine for small projects, CSS Modules offer a more scalable and maintainable approach to styling. Let’s convert the styles in RecipeCard to use CSS Modules.
First, create a file named RecipeCard.module.css in the components directory. Add the following CSS:
/* components/RecipeCard.module.css */
.recipeCard {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
text-decoration: none;
color: #333;
}
.recipeCard img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 4px;
margin-bottom: 8px;
}
.recipeCard h3 {
font-size: 1.2rem;
margin-bottom: 8px;
}
.recipeCard:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
Now, update RecipeCard.js to use the CSS Module:
// components/RecipeCard.js
import Link from 'next/link';
import styles from './RecipeCard.module.css';
const RecipeCard = ({ recipe }) => {
return (
<div className={styles.recipeCard}>
<Link href={`/recipe/${recipe.id}`}>
<a>
<img src={recipe.image} alt={recipe.title} />
<h3>{recipe.title}</h3>
<p>{recipe.description}</p>
</a>
</Link>
</div>
);
};
export default RecipeCard;
Import the CSS module (import styles from './RecipeCard.module.css';) and then use the class names as properties of the styles object (e.g., className={styles.recipeCard}). This approach ensures that your CSS class names are locally scoped to the component, preventing conflicts with other styles in your application. Repeat this process for the other components to adopt CSS Modules. CSS Modules help avoid naming collisions and make your CSS more modular.
Step 7: Deployment
Once you’re happy with your application, it’s time to deploy it. Next.js applications are easy to deploy. Here are a few popular options:
- Vercel: Vercel is the easiest way to deploy Next.js applications. It’s built by the same company that created Next.js, and deployment is as simple as connecting your GitHub repository. You can deploy with a single command:
vercel(after installing the Vercel CLI globally:npm install -g vercel). - Netlify: Netlify is another excellent option, offering similar ease of deployment and a global CDN. Connect your Git repository, and Netlify will automatically build and deploy your application.
- Other Platforms: You can also deploy to platforms like AWS, Google Cloud, or Azure, but this usually involves more configuration.
For this tutorial, let’s assume you’re deploying to Vercel. After deploying, you’ll receive a URL where your live recipe app will be accessible. Make sure you have a Vercel account and the Vercel CLI installed. Then, run vercel in your project directory and follow the prompts.
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Incorrect File Paths: Double-check your file paths when importing components or data. A simple typo can break your application.
- Missing Dependencies: Ensure you’ve installed all the necessary dependencies. Run
npm installoryarn installto install them. - Incorrect CSS Module Usage: Make sure you’re importing the CSS module correctly and using the class names as properties of the
stylesobject. - Routing Errors: Pay close attention to your routing configuration, especially with dynamic routes. Verify that your file names and route parameters match.
- Data Fetching Issues: When fetching data from an API (or your mock data), make sure you’re handling potential errors and displaying appropriate messages to the user.
Key Takeaways
- Next.js simplifies web development with features like server-side rendering and static site generation.
- Components are the building blocks of your React applications.
- Dynamic routes allow you to create flexible and user-friendly navigation.
- CSS Modules help you write maintainable and scalable CSS.
- Deployment is straightforward with platforms like Vercel and Netlify.
FAQ
Q: How can I add more recipes to the app?
A: Simply add more recipe objects to the recipes array in your data.js file. Remember to include the required properties (id, title, description, image, ingredients, instructions).
Q: How do I handle images?
A: Place your image files in the public/images directory. Next.js automatically serves files in the public directory. You can then reference the images in your components using paths like /images/carbonara.jpg.
Q: How can I improve the app’s performance?
A: Optimize images, use code splitting, and consider using server-side rendering or static site generation for improved performance. Lazy loading images can also significantly improve initial page load times.
Q: How do I add more features, like user authentication or the ability to save recipes?
A: This would involve integrating with a backend (e.g., using a database and a serverless function). You would need to add user interface components for login/signup, and then implement the logic for saving recipes to the database. Libraries and services like Firebase, Supabase, or AWS Amplify can greatly simplify this process.
This project provides a solid foundation for building a functional and user-friendly recipe application. By using Next.js, you’ve created a web app that is both performant and SEO-friendly. Remember to test your application thoroughly, handle errors gracefully, and continuously improve your code as you learn more. The ability to create web applications with Next.js is a valuable skill in today’s web development landscape. Experiment with more advanced features, explore different styling options, and consider integrating a database to store and manage your recipes for a more complete and useful application. The possibilities are vast, and the journey of learning and building is what makes software development so rewarding.
