A performant, SEO-friendly game portal listing 20,000+ titles requires more than architecture diagrams—it needs real code. In this walkthrough, we’ll use Next.js, Strapi (headless CMS), and Algolia search to deliver fast page loads, dynamic filtering, and easy content management.
1. Project Setup
# Create Next.js app
npx create-next-app risequest-portal
cd risequest-portal
# Install dependencies
npm install axios algoliasearch react-window tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
Tailwind CSS (tailwind.config.js
):
module.exports = {
content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
theme: { extend: {} },
plugins: [],
};
2. Headless CMS with Strapi
Spin up Strapi for game metadata:
npx create-strapi-app cms --quickstart
In Strapi’s content-type builder, define Game with fields:
-
title
(String) -
slug
(UID, based on title) -
thumbnail
(Media) -
categories
(Relation, many-to-many with Category) -
gameUrl
(String)
Populate a handful of entries, then expose the REST API at http://localhost:1337/api/games
.
3. Fetching Data & Static Generation
In pages/index.js
:
import axios from 'axios';
import GameCard from '../components/GameCard';
export async function getStaticProps() {
const { data } = await axios.get('http://localhost:1337/api/games?populate=thumbnail,categories');
// Simplify the payload
const games = data.data.map(({ id, attributes }) => ({
id,
title: attributes.title,
slug: attributes.slug,
thumbnail: attributes.thumbnail.data.attributes.url,
categories: attributes.categories.map(cat => cat.name)
}));
return { props: { games } };
}
export default function Home({ games }) {
return (
<main className="p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{games.map(game => (
<GameCard key={game.id} {...game} />
))}
</main>
);
}
4. Building the GameCard
Component
components/GameCard.js
:
export default function GameCard({ title, slug, thumbnail, categories }) {
return (
<div className="rounded-xl shadow-lg overflow-hidden">
<img src={thumbnail} alt={`${title} thumbnail`} className="w-full h-40 object-cover" />
<div className="p-3">
<h3 className="text-lg font-semibold">{title}</h3>
<p className="text-sm text-gray-500">{categories.join(', ')}</p>
<a
href={`/game/${slug}`}
className="mt-2 inline-block bg-blue-600 text-white px-4 py-2 rounded-md"
>
Play Now
</a>
</div>
</div>
);
}
5. Dynamic Game Pages
pages/game/[slug].js
:
import axios from 'axios';
export async function getStaticPaths() {
const { data } = await axios.get('http://localhost:1337/api/games');
const paths = data.data.map(game => ({ params: { slug: game.attributes.slug } }));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const { data } = await axios.get(
`http://localhost:1337/api/games?filters[slug][$eq]=${params.slug}&populate=thumbnail`
);
const game = data.data[0].attributes;
return { props: { game } };
}
export default function GamePage({ game }) {
return (
<article className="max-w-2xl mx-auto p-4">
<h1 className="text-3xl font-bold mb-4">{game.title}</h1>
<img src={game.thumbnail.data.attributes.url} alt={game.title} className="mb-4" />
<iframe
src={game.gameUrl}
title={game.title}
className="w-full h-[600px] border rounded"
allowFullScreen
></iframe>
</article>
);
}
6. Instant Search with Algolia
-
Indexing: In a one-off script (
scripts/index-to-algolia.js
):
const algoliasearch = require('algoliasearch');
const axios = require('axios');
const client = algoliasearch('YourAppID', 'YourAdminAPIKey');
const index = client.initIndex('games');
async function run() {
const { data } = await axios.get('http://localhost:1337/api/games?populate=categories');
const records = data.data.map(({ id, attributes }) => ({
objectID: id,
title: attributes.title,
slug: attributes.slug,
categories: attributes.categories.map(c => c.name).join(' '),
}));
await index.saveObjects(records);
console.log('✅ Indexed to Algolia');
}
run();
-
Front-End Search:
components/Search.js
:
import { useState, useEffect } from 'react';
import algoliasearch from 'algoliasearch';
const client = algoliasearch('YourAppID', 'YourSearchOnlyKey');
const index = client.initIndex('games');
export default function Search({ onResults }) {
const [query, setQuery] = useState('');
useEffect(() => {
if (!query) return onResults([]);
index.search(query, { hitsPerPage: 20 }).then(({ hits }) => {
onResults(hits);
});
}, [query]);
return (
<input
className="w-full p-2 border rounded mb-4"
placeholder="Search games..."
value={query}
onChange={e => setQuery(e.target.value)}
/>
);
}
Integrate <Search>
at the top of your index.js
and pass results into your grid.
7. Deployment and Optimization
- Vercel: Push this repo to GitHub and connect on Vercel for instant Next.js deployments.
- Environment Variables: Store API endpoints and Algolia keys in Vercel’s dashboard.
- Image CDN: Configure Next.js Image Optimization for remote Strapi images.
Conclusion
With these code examples—from Next.js page generation and React components to Algolia search indexing—you now have the template to launch your own hypercasual game portal. Remember to monitor performance with Lighthouse, secure your APIs, and enrich UX with lazy loading.
⭐️ Play 20,000+ free games now: RiseQuestGame
Happy coding!
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.