Word games have taken the world by storm, and one of the most popular is Wordle. Its simple yet addictive gameplay has captivated millions. In this tutorial, we’ll dive into building a simplified version of Wordle using Next.js, a powerful React framework for building web applications. This project is perfect for beginners and intermediate developers looking to sharpen their skills and learn about state management, user input handling, and basic game logic.
Why Build a Wordle Clone?
Building a Wordle clone is a fantastic learning experience for several reasons:
- Interactive User Interface: You’ll learn how to create an interactive UI where users can input text and see immediate feedback.
- State Management: Wordle relies heavily on managing the game’s state (the word, the guesses, the feedback). You’ll gain practical experience with state management in React using Next.js.
- Logic and Algorithms: Implementing the game logic (checking guesses, providing feedback) introduces you to basic algorithms and conditional logic.
- Component-Based Architecture: You’ll practice breaking down a complex application into reusable components.
- Real-World Application: You’ll build something fun and engaging that you can share and showcase.
Prerequisites
Before we begin, make sure you have the following:
- Node.js and npm (or yarn) installed on your machine.
- Basic familiarity with HTML, CSS, and JavaScript.
- A code editor (like VS Code) for writing your code.
Setting Up Your 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 wordle-clone
cd wordle-clone
This command creates a new Next.js project named “wordle-clone” and navigates you into the project directory. Next.js provides a great development experience with features like hot reloading and built-in routing.
Project Structure Overview
Your project directory will look something like this:
wordle-clone/
├── node_modules/
├── public/
│ └── ...
├── src/
│ ├── app/
│ │ ├── page.js
│ │ └── layout.js
│ └── ...
├── .gitignore
├── next.config.js
├── package-lock.json
├── package.json
├── README.md
└── ...
The key directories and files we’ll be working with are:
src/app/page.js: This is where we’ll build our main Wordle component.public/: This directory is for static assets like images and fonts.
Creating the Game Components
Let’s create the components for our Wordle game. We’ll break down the game into smaller, manageable pieces. Inside the src/app directory, we’ll primarily work with the page.js file. We will create the following components:
- Board: Represents the grid where the user enters their guesses and receives feedback.
- Row: Represents a single row in the board.
- Square: Represents a single square within a row.
- Keyboard: A visual keyboard the user can use for input.
- Game Over Modal (Optional): Displays the game result (win or lose).
1. The Board Component
First, let’s create the Board component. This component will manage the game board, displaying the user’s guesses and the feedback (correct letters, incorrect letters, etc.).
Open src/app/page.js and add the following code:
'use client';
import React, { useState, useEffect } from 'react';
const WORD_LENGTH = 5;
const MAX_ATTEMPTS = 6;
const getRandomWord = () => {
const wordList = ['REACT', 'QUERY', 'BUILD', 'FRAME', 'WORLD']; // Replace with a larger word list in a real app
return wordList[Math.floor(Math.random() * wordList.length)];
};
function Board() {
const [secretWord, setSecretWord] = useState(getRandomWord);
const [guesses, setGuesses] = useState(Array(MAX_ATTEMPTS).fill(Array(WORD_LENGTH).fill('')));
const [currentGuessIndex, setCurrentGuessIndex] = useState(0);
const [currentLetterIndex, setCurrentLetterIndex] = useState(0);
const [gameWon, setGameWon] = useState(false);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
if (currentGuessIndex === MAX_ATTEMPTS && !gameWon) {
setGameOver(true);
}
}, [currentGuessIndex, gameWon]);
const handleLetterInput = (letter) => {
if (currentLetterIndex {
if (currentLetterIndex > 0 && !gameOver) {
const newGuesses = [...guesses];
newGuesses[currentGuessIndex][currentLetterIndex - 1] = '';
setGuesses(newGuesses);
setCurrentLetterIndex(currentLetterIndex - 1);
}
};
const handleSubmitGuess = () => {
if (currentLetterIndex === WORD_LENGTH && !gameOver) {
const guess = guesses[currentGuessIndex].join('');
if (guess === secretWord) {
setGameWon(true);
setGameOver(true);
} else {
setCurrentGuessIndex(currentGuessIndex + 1);
setCurrentLetterIndex(0);
}
}
};
const renderSquares = (row, rowIndex) => {
return row.map((letter, letterIndex) => (
<div style="{{">
{letter}
</div>
));
};
const getSquareColor = (rowIndex, letterIndex) => {
const guess = guesses[rowIndex].join('');
if (guess.length !== WORD_LENGTH) {
return 'lightgray';
}
const secretWordChars = secretWord.split('');
const letter = guess[letterIndex];
if (!letter) {
return 'lightgray';
}
if (letter === secretWordChars[letterIndex]) {
return 'green';
}
if (secretWordChars.includes(letter)) {
return 'yellow';
}
return 'gray';
};
const renderRows = () => {
return guesses.map((row, rowIndex) => (
<div>
{renderSquares(row, rowIndex)}
</div>
));
};
return (
<div>
{renderRows()}
{gameOver && (
<div>
{gameWon ? <p>You won!</p> : <p>You lost! The word was {secretWord}</p>}
<button> window.location.reload()}>Play Again</button>
</div>
)}
</div>
);
}
export default function Home() {
return (
<main>
<h1>Wordle Clone</h1>
</main>
);
}
Let’s break down this code:
- Imports: We import `useState` and `useEffect` from React.
- Constants:
WORD_LENGTHandMAX_ATTEMPTSdefine the game’s parameters. - getRandomWord: A simple function to select a random word from a predefined list. In a real app, you would fetch a larger word list from an API or a database.
- State Variables:
secretWord: The word the player needs to guess.guesses: A 2D array representing the player’s guesses. Each element in the array is a string (letter).currentGuessIndex: The index of the current guess (0-5).currentLetterIndex: The index of the current letter within the current guess (0-4).gameWon: A boolean indicating whether the player has won.gameOver: A boolean indicating whether the game is over.
- useEffect: Checks if the game is over after each guess.
- handleLetterInput: Handles the input of a letter.
- handleDeleteLetter: Handles deleting a letter.
- handleSubmitGuess: Handles submitting the guess.
- renderSquares: Renders the individual squares for each row.
- getSquareColor: Determines the background color of each square based on the guess.
- renderRows: Renders all the rows on the board.
- Return: The JSX for the board component, including the rows and the game over modal.
In this code, we have a basic board. It displays the squares, but we need to create the CSS to style it. We also need to implement the input via a keyboard component (next section).
2. Styling the Board (CSS)
Create a file named src/app/globals.css (if it doesn’t already exist) and add the following CSS styles:
/* src/app/globals.css */
.container {
display: flex;
flex-direction: column;
align-items: center;
font-family: sans-serif;
}
h1 {
margin-bottom: 20px;
}
.board {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.row {
display: flex;
margin-bottom: 5px;
}
.square {
width: 40px;
height: 40px;
border: 1px solid #ddd;
margin-right: 5px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
font-weight: bold;
text-transform: uppercase;
}
.game-over-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid black;
padding: 20px;
z-index: 10;
text-align: center;
}
.game-over-modal button {
margin-top: 10px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
.green {
background-color: #6aaa64;
color: white;
}
.yellow {
background-color: #c9b458;
color: white;
}
.gray {
background-color: #787c7e;
color: white;
}
This CSS provides basic styling for the container, board, rows, squares, and the game over modal. The colors are inspired by the original Wordle game.
3. The Keyboard Component
Now, let’s create the Keyboard component. This will allow the user to input letters into the board.
Create a new file named src/app/keyboard.js and add the following code:
'use client';
import React from 'react';
const Keyboard = ({ onLetterInput, onDeleteLetter, onSubmitGuess }) => {
const keyboardRows = [
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
['Z', 'X', 'C', 'V', 'B', 'N', 'M'],
];
return (
<div>
{keyboardRows.map((row, rowIndex) => (
<div>
{row.map((letter) => (
<button> onLetterInput(letter)}
>
{letter}
</button>
))}
{rowIndex === 1 && (
<button>
DEL
</button>
)}
{rowIndex === 2 && (
<button>
ENTER
</button>
)}
</div>
))}
</div>
);
};
export default Keyboard;
Let’s break down this code:
- Keyboard Rows: An array of arrays representing the keyboard layout.
- onLetterInput, onDeleteLetter, onSubmitGuess: These are props that will be passed from the Board component. They are functions that handle the user’s input.
- Mapping the rows and keys: The component maps over the keyboardRows array and renders buttons for each letter.
- DEL and ENTER buttons: Special buttons for deleting letters and submitting guesses are included.
Now, let’s add the keyboard styling. Add the following CSS to src/app/globals.css:
/* src/app/globals.css */
.keyboard {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.keyboard-row {
display: flex;
margin-bottom: 5px;
}
.keyboard-button {
width: 30px;
height: 40px;
margin-right: 3px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
background-color: #eee;
}
.keyboard-button:hover {
background-color: #ddd;
}
4. Integrating the Keyboard into the Board
Now we need to integrate the keyboard component into the Board component. Modify your src/app/page.js file to include the keyboard:
'use client';
import React, { useState, useEffect } from 'react';
import Keyboard from './keyboard';
const WORD_LENGTH = 5;
const MAX_ATTEMPTS = 6;
const getRandomWord = () => {
const wordList = ['REACT', 'QUERY', 'BUILD', 'FRAME', 'WORLD']; // Replace with a larger word list in a real app
return wordList[Math.floor(Math.random() * wordList.length)];
};
function Board() {
const [secretWord, setSecretWord] = useState(getRandomWord);
const [guesses, setGuesses] = useState(Array(MAX_ATTEMPTS).fill(Array(WORD_LENGTH).fill('')));
const [currentGuessIndex, setCurrentGuessIndex] = useState(0);
const [currentLetterIndex, setCurrentLetterIndex] = useState(0);
const [gameWon, setGameWon] = useState(false);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
if (currentGuessIndex === MAX_ATTEMPTS && !gameWon) {
setGameOver(true);
}
}, [currentGuessIndex, gameWon]);
const handleLetterInput = (letter) => {
if (currentLetterIndex {
if (currentLetterIndex > 0 && !gameOver) {
const newGuesses = [...guesses];
newGuesses[currentGuessIndex][currentLetterIndex - 1] = '';
setGuesses(newGuesses);
setCurrentLetterIndex(currentLetterIndex - 1);
}
};
const handleSubmitGuess = () => {
if (currentLetterIndex === WORD_LENGTH && !gameOver) {
const guess = guesses[currentGuessIndex].join('');
if (guess === secretWord) {
setGameWon(true);
setGameOver(true);
} else {
setCurrentGuessIndex(currentGuessIndex + 1);
setCurrentLetterIndex(0);
}
}
};
const renderSquares = (row, rowIndex) => {
return row.map((letter, letterIndex) => (
<div style="{{">
{letter}
</div>
));
};
const getSquareColor = (rowIndex, letterIndex) => {
const guess = guesses[rowIndex].join('');
if (guess.length !== WORD_LENGTH) {
return 'lightgray';
}
const secretWordChars = secretWord.split('');
const letter = guess[letterIndex];
if (!letter) {
return 'lightgray';
}
if (letter === secretWordChars[letterIndex]) {
return 'green';
}
if (secretWordChars.includes(letter)) {
return 'yellow';
}
return 'gray';
};
const renderRows = () => {
return guesses.map((row, rowIndex) => (
<div>
{renderSquares(row, rowIndex)}
</div>
));
};
return (
<div>
{renderRows()}
{gameOver && (
<div>
{gameWon ? <p>You won!</p> : <p>You lost! The word was {secretWord}</p>}
<button> window.location.reload()}>Play Again</button>
</div>
)}
</div>
);
}
export default function Home() {
return (
<main>
<h1>Wordle Clone</h1>
</main>
);
}
We import the Keyboard component and pass the necessary event handlers (handleLetterInput, handleDeleteLetter, and handleSubmitGuess) as props. This allows the keyboard to interact with the board.
Testing and Refining
Now, run your Next.js application using npm run dev or yarn dev. Open your browser to http://localhost:3000 (or the address shown in your terminal). You should see the Wordle board and the keyboard. Test the following:
- Letter Input: Click the keyboard buttons to enter letters. They should appear in the squares.
- Backspace/Delete: Click the DEL button to remove letters.
- Enter/Submit: Click the ENTER button to submit your guess. The colors of the squares should update based on your guess.
- Game Over: After six incorrect guesses, or if you guess the word, the game over modal should appear.
Here are some common issues and how to fix them:
- Incorrect Colors: Double-check your
getSquareColorfunction. Make sure it correctly compares the guessed letters with the secret word and applies the correct colors. Also, check for case sensitivity (the secret word and the guess should be uppercased). - Keyboard Not Working: Ensure that your event handlers (
handleLetterInput,handleDeleteLetter,handleSubmitGuess) are correctly passed as props to the Keyboard component. - Input Not Appearing: Verify that the
currentLetterIndexis correctly incremented and that theguessesarray is being updated correctly. - Game Over Not Triggering: Ensure that both the win and loss conditions correctly set the
gameOverstate variable to true.
Enhancements and Next Steps
This is a basic Wordle clone. Here are some ways you can enhance it:
- Word List: Replace the hardcoded word list with a larger list fetched from an API or a local JSON file. Consider using a dictionary API.
- Error Handling: Handle invalid word guesses (words that are not in the word list).
- User Interface Improvements:
- Add animations (e.g., a flip animation for the squares).
- Improve the keyboard styling and feedback (e.g., change the color of the keyboard keys based on the guess feedback).
- Add a visual indicator of the current guess (e.g., highlight the current row).
- Accessibility: Improve accessibility for users with disabilities (e.g., using ARIA attributes).
- Persistence: Save the game state to local storage so the user can resume the game later.
- Mobile Responsiveness: Ensure the game looks good on different screen sizes using responsive design techniques.
- Difficulty Levels: Allow the user to choose the word length or the number of guesses.
- Statistics: Track the user’s win/loss ratio, number of games played, and average guess count.
- Sharing: Add a feature to share the results on social media.
Key Takeaways
This tutorial has walked you through creating a simplified Wordle clone using Next.js. You’ve learned about:
- Setting up a Next.js project.
- Creating and organizing React components.
- Managing state with the
useStatehook. - Handling user input and events.
- Implementing game logic.
- Styling components with CSS.
By building this project, you’ve gained practical experience with fundamental concepts in web development and React. Remember to experiment with the code, try out the enhancements suggested above, and most importantly, have fun! This project should give you a solid foundation for building more complex interactive web applications.
FAQ
Here are some frequently asked questions:
- How can I deploy this application? You can deploy your Next.js application to platforms like Vercel, Netlify, or AWS. Vercel is particularly well-suited for Next.js projects as it provides automatic deployments and easy scaling.
- Where can I find a word list? You can find word lists online from various sources. Consider using a dictionary API (like the Merriam-Webster API) to fetch words. Make sure to handle API rate limits and terms of service.
- How can I add animations? You can use CSS animations or libraries like Framer Motion to add animations to your components. For example, you could animate the squares flipping over when a guess is submitted.
- How do I handle invalid word guesses? Before submitting the guess, check if the entered word is in your word list. If it’s not, display an error message to the user and prevent the guess from being submitted.
- How can I improve the keyboard feedback? You can change the background color of the keyboard keys based on the guess feedback (green for correct, yellow for present, gray for absent). You can also disable the keys for letters that are not in the word.
Building this Wordle clone is a great way to put your Next.js skills to the test and is a fun project to showcase. With each line of code, you are not only constructing a game, but also refining your ability to think through problems and design solutions in the world of web development. Take the skills you’ve acquired here and apply them to your future projects!
