I run a free HTML5 games portal, SPC โ Super Play Combo. It's built on WordPress with the GeneratePress theme and has over 3,000 games from the GameMonetize and GamePix feeds. Over the last few weeks I rebuilt the homepage in a Poki-like style, with one horizontal row per category. It looked easy. It wasn't.
Here are the real problems I ran into and how I fixed them.
1. Category rows with no duplicates
Each category (Puzzle, Arcade, Match-3, Mahjong, Quiz, Sports, Racingโฆ) has its own page with its own game list. The homepage shows one row per category, built by a script called motore-playcombo.js.
The feeds use English category names, while my site uses Italian ones, so I made a map:
const CATEGORIE_HOME = [
'puzzle', 'sparabolle', 'arcade', 'match-3', 'mahjong',
'trivia', 'gamesforgirls', 'sports', 'racing',
'card', 'adventure', 'platformer', '2048'
];
Golden rule: each game appears in exactly one row. A Set of already-used ids does the job:
const alreadyShown = new Set();
function removeDuplicates(games) {
return games.filter(g => {
const key = g.id || g.url || g.title;
if (alreadyShown.has(key)) return false;
alreadyShown.add(key);
return true;
});
}
Rows further down only load when they're about to scroll into view:
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
loadRow(e.target.dataset.category);
observer.unobserve(e.target);
}
});
}, { rootMargin: '300px' });
2. "It works when I'm logged in, but not in incognito"
The Quiz and Card rows were full when I was logged in as admin, but empty for visitors. The culprit was LiteSpeed Cache. For visitors who aren't logged in, it optimizes the page and turns the game list inside the HTML into base64. My script was looking for plain text and found nothing.
The fix is to try decoding it:
function readList(text) {
try { return JSON.parse(text); } catch (e) {}
try { return JSON.parse(atob(text)); } catch (e) {}
return [];
}
Lesson: always test in incognito. What you see as an admin is not what your users see.
3. The little ?v= number that saves your life
The script is loaded like this:
<script src="/js/motore-playcombo.js?v=16"></script>
?v=16 does nothing to the code. It only tells the browser (and the cache, and the PWA service worker) that the file is new. Every time I upload a new version, I:
- bump the number on every page that loads the script;
- purge the LiteSpeed cache.
Forget this and you'll get 404 errors for files you already deleted, because the browser is still running the old version. Ask me how I know.
4. Thumbnails: from wsrv.nl to i0.wp.com
To make thumbnails lighter, I was running them through an image proxy. Then I found out that the service's robots.txt blocks Googlebot, so Google couldn't see my images. I switched to WordPress's free image CDN:
function optimizeImg(url) {
if (url.includes('gamepix') || url.includes('?')) return url;
const u = new URL(url);
return `https://i0.wp.com/${u.host}${u.pathname}?w=240&quality=75`;
}
I also added a small safety net: if the optimized image fails to load, it falls back to the original.
img.onerror = () => { img.onerror = null; img.src = original; };
The result is a much faster homepage.
5. A bit of (lightweight) style
One last touch: the background has game-themed icons (a joypad, a coin, a star, a heart) made with pure CSS and no shadows, so phones don't struggle. With body.page-id-XXXX I can give each page its own look, like floating yellow stars only on the Girls page.
Takeaways
- Always test in incognito and on a phone.
- The cache is your friend, until you forget about it.
- A wrong (or unchanged)
?v=can cost you hours. - Check the
robots.txtof any third-party service you rely on. - PWA errors often go away by themselves after a few minutes, so don't panic right away.
If you want to see the result, the games are on playcombo.it. Free, as always. ๐ฎ
Top comments (0)