DEV Community

mufeng
mufeng

Posted on

Building a Scalable Hypercasual Game Portal with Next.js: From Data to Deployment

Image description
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
Enter fullscreen mode Exit fullscreen mode

Tailwind CSS (tailwind.config.js):

module.exports = {
  content: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
  theme: { extend: {} },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

2. Headless CMS with Strapi

Spin up Strapi for game metadata:

npx create-strapi-app cms --quickstart
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Instant Search with Algolia

  1. 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();
Enter fullscreen mode Exit fullscreen mode
  1. 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)}
       />
     );
   }
Enter fullscreen mode Exit fullscreen mode

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.