SEO
Overview
Playupi’s SEO strategy is built around the fact that every game has a unique, crawlable page with rich metadata. The platform uses server-side rendering (Next.js SSR) so all content is available to search engines without JavaScript execution.
Key Advantages
- Thousands of unique pages — each game, category, and author gets a dedicated URL
- SSR by default — all pages are server-rendered and cacheable at the edge
- Structured data — game pages include JSON-LD markup for rich search results
- Fast load times — static assets on CDN, game content loads lazily in iframes
URL Structure
Clean, readable, keyword-rich URLs:
| Page | URL Pattern | Example |
|---|---|---|
| Home | / | playupi.com |
| Category | /category/:slug | playupi.com/category/action |
| Game detail | /games/:slug | playupi.com/games/subway-surfers |
| Author | /authors/:slug | playupi.com/authors/kiloo-games |
| Search | /search?q=:query | playupi.com/search?q=puzzle |
URL Rules
| Rule | Implementation |
|---|---|
| Lowercase only | Slugs are always lowercase |
| Hyphens as separators | my-game-title, never my_game_title or MyGameTitle |
| No trailing slashes | /games/tetris not /games/tetris/. Redirect if accessed with trailing slash |
| No IDs in URLs | Use slugs, not UUIDs. /games/subway-surfers not /games/abc-123 |
| Max length | Slugs capped at 80 characters |
| Slug generation | Auto-generated from title on creation. Editable by admin. Must be unique |
| Slug persistence | Once a game is Visible, changing the slug creates a 301 redirect from the old URL |
Meta Tags
Per-Page Tags
| Page | <title> | <meta description> | OG Image |
|---|---|---|---|
| Home | Playupi — Free Browser Games Online | Play hundreds of free browser games instantly. No downloads, no installs. | Brand OG image |
| Category | {Category} Games — Playupi | Play the best {category} games online for free on Playupi. | Brand OG image |
| Author | Games by {Author} — Playupi | Play all games from {Author} on Playupi. | Brand OG image |
| Game Detail | {Game Title} — Play Free on Playupi | First 155 chars of game description + ”…” | Game thumbnail |
| Search | Search: {query} — Playupi | Find free browser games on Playupi. | Brand OG image |
| 404 | Page Not Found — Playupi | This page doesn’t exist. Browse our free browser games. | Brand OG image |
Title Rules
- Brand name (“Playupi”) always at the end, separated by ” — ”
- Page-specific keyword at the start (most important for SEO)
- Max 60 characters (Google truncates longer titles)
Description Rules
- Max 155 characters
- Include a call to action (“Play”, “Browse”, “Find”)
- Game descriptions: use the first 155 chars of the admin-provided description, not auto-generated text
- Never duplicate titles as descriptions
Open Graph & Social Sharing
Every page includes Open Graph and Twitter Card meta tags for rich previews when shared on social media.
Tags
<!-- Open Graph --><meta property="og:type" content="website" /><meta property="og:site_name" content="Playupi" /><meta property="og:title" content="{Page Title}" /><meta property="og:description" content="{Page Description}" /><meta property="og:url" content="https://playupi.com/games/subway-surfers" /><meta property="og:image" content="https://playupi.com/og/games/subway-surfers.png" /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="630" />
<!-- Twitter Card --><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content="{Page Title}" /><meta name="twitter:description" content="{Page Description}" /><meta name="twitter:image" content="https://playupi.com/og/games/subway-surfers.png" />OG Image Strategy
| Page Type | OG Image | Size |
|---|---|---|
| Home, Category, Author, Search, 404 | Brand OG image: “Playupi” wordmark + tagline on branded background | 1200x630 |
| Game Detail | Game thumbnail overlaid on branded template (game icon + title + “Play Free on Playupi”) | 1200x630 |
Game OG images can be dynamically generated using Next.js ImageResponse (or @vercel/og):
import { ImageResponse } from 'next/og'
export default async function OGImage({ params }) { const game = await getGame(params.slug) return new ImageResponse( <div style={{ /* branded template with game.title, game.thumbnailUrl */ }} />, { width: 1200, height: 630 } )}Structured Data (JSON-LD)
Game Detail Page
{ "@context": "https://schema.org", "@type": "VideoGame", "name": "Subway Surfers", "description": "Run, dodge trains, and collect coins in this endless runner.", "url": "https://playupi.com/games/subway-surfers", "image": "https://playupi.com/thumbnails/subway-surfers.png", "author": { "@type": "Organization", "name": "Kiloo Games", "url": "https://playupi.com/authors/kiloo-games" }, "genre": ["Action", "Arcade"], "playMode": "SinglePlayer", "gamePlatform": ["Web Browser"], "operatingSystem": "Any", "applicationCategory": "Game", "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }, "aggregateRating": { "@type": "AggregateRating", "ratingValue": 4.2, "ratingCount": 1580, "bestRating": 5, "worstRating": 1 }}Note:
aggregateRatingshould only be included once a star-rating system is implemented (future feature). For MVP, use like/dislike ratio to approximate, or omit the rating block.
Category Page
{ "@context": "https://schema.org", "@type": "CollectionPage", "name": "Action Games", "description": "Play the best action games online for free on Playupi.", "url": "https://playupi.com/category/action", "mainEntity": { "@type": "ItemList", "itemListElement": [ { "@type": "ListItem", "position": 1, "url": "https://playupi.com/games/subway-surfers" } ] }}Home Page
{ "@context": "https://schema.org", "@type": "WebSite", "name": "Playupi", "url": "https://playupi.com", "description": "Play hundreds of free browser games instantly.", "potentialAction": { "@type": "SearchAction", "target": "https://playupi.com/search?q={search_term_string}", "query-input": "required name=search_term_string" }}The SearchAction enables Google’s sitelinks search box (search directly from the SERP).
Sitemap
Generation
Auto-generate sitemap.xml at build time or on-demand (ISR):
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://playupi.com/</loc> <changefreq>daily</changefreq> <priority>1.0</priority> </url> <url> <loc>https://playupi.com/category/action</loc> <changefreq>daily</changefreq> <priority>0.8</priority> </url> <url> <loc>https://playupi.com/games/subway-surfers</loc> <lastmod>2026-02-10</lastmod> <changefreq>weekly</changefreq> <priority>0.7</priority> </url></urlset>Rules
| Rule | Detail |
|---|---|
| Only visible games | Draft and Hidden games are excluded |
| Sitemap index | If > 50K URLs, split into multiple sitemaps with a sitemap index |
lastmod | Use game.updatedAt for game pages, current date for Home/category |
changefreq | Home: daily. Categories: daily. Games: weekly. Authors: weekly |
priority | Home: 1.0. Categories: 0.8. Games: 0.7. Authors: 0.5 |
| Auto-submit | Reference sitemap in robots.txt. Google auto-discovers it |
Next.js Implementation
export default async function sitemap() { const games = await getVisibleGames() const categories = await getCategories()
return [ { url: 'https://playupi.com', changeFrequency: 'daily', priority: 1 }, ...categories.map(cat => ({ url: `https://playupi.com/category/${cat.slug}`, changeFrequency: 'daily', priority: 0.8, })), ...games.map(game => ({ url: `https://playupi.com/games/${game.slug}`, lastModified: game.updatedAt, changeFrequency: 'weekly', priority: 0.7, })), ]}Robots.txt
User-agent: *Allow: /Disallow: /api/Disallow: /admin/
Sitemap: https://playupi.com/sitemap.xml| Rule | Reason |
|---|---|
Block /api/ | API responses are not useful as search results |
Block /admin/ | Admin dashboard should never be indexed |
| Allow everything else | All public pages should be crawlable |
Canonical URLs
Every page includes a <link rel="canonical"> tag pointing to the definitive URL.
| Scenario | Canonical |
|---|---|
| Normal page | Self-referencing (https://playupi.com/games/tetris) |
| Page with query params (pagination, filters) | Base URL without params (https://playupi.com/category/action) |
| Old slug (after rename) | 301 redirect to new slug — no canonical needed |
| Duplicate content (same game, different URL) | Point to the primary URL |
<link rel="canonical" href="https://playupi.com/games/tetris" />Pagination SEO
Game list pages (Home, category, author) use pagination. Search engines need to understand the relationship between pages.
Load More / Infinite Scroll
If using “Load More” button or infinite scroll:
- The initial page contains the first N games and is the only URL indexed
- Loaded-more content is added to the DOM via JavaScript — not crawlable
- This is fine for SEO because the most relevant (highest-ranked) games are on the initial page
Paginated URLs (alternative)
If using ?page=N pagination:
<!-- Page 1 --><link rel="next" href="https://playupi.com/category/action?page=2" />
<!-- Page 2 --><link rel="prev" href="https://playupi.com/category/action" /><link rel="next" href="https://playupi.com/category/action?page=3" />
<!-- Last page --><link rel="prev" href="https://playupi.com/category/action?page=4" />Canonical for all paginated pages points to the base URL (page 1 without ?page=).
Core Web Vitals
Google uses Core Web Vitals as a ranking signal. Gaming platforms are at risk for poor scores due to heavy iframe content.
Targets
| Metric | Target | Risk Area |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Game grid images, hero area |
| INP (Interaction to Next Paint) | < 200ms | Search input, filter changes, card clicks |
| CLS (Cumulative Layout Shift) | < 0.1 | Image loading without dimensions, late-loading ads |
Optimizations
| Optimization | Detail |
|---|---|
| Image dimensions | Always set width and height on game thumbnails to prevent CLS |
| Lazy loading | Game thumbnails below the fold use loading="lazy" |
| Priority hints | First 6 game card images use fetchpriority="high" |
| Image format | Serve WebP with JPEG fallback. Use Next.js <Image> for automatic optimization |
| Iframe lazy load | Game iframe uses loading="lazy" — only loads when user scrolls to it (desktop) or taps Play (mobile) |
| Font loading | Inter loaded via next/font (self-hosted, no layout shift). Use font-display: swap |
| SSR + Edge caching | Game list pages are SSR-cached at the edge (Vercel). TTL: 60s with stale-while-revalidate |
| Preconnect | <link rel="preconnect"> to broker CDN domains for faster iframe load |
<link rel="preconnect" href="https://html5.gamedistribution.com" /><link rel="dns-prefetch" href="https://html5.gamedistribution.com" />Internal Linking
Strong internal linking helps search engines discover all pages and understand content hierarchy.
Link Structure
Home ──► Category pages ──► Game pages │ │ └──► Game pages └──► Author page └──► Related category pages └──► Recommended game pagesPer-Page Internal Links
| Page | Links To |
|---|---|
| Home | All category pages (sidebar), top game pages (grid) |
| Category | Game pages in that category, other categories (sidebar) |
| Game Detail | Author page, category pages (tags), recommended games, “more by author” |
| Author | Game pages by that author, categories (sidebar) |
Anchor Text
- Game cards: game title as anchor text (visible on hover, always in DOM for crawlers)
- Category links: category name
- Author links: author name
- Avoid generic anchor text (“click here”, “read more”)
Image SEO
Alt Text
| Image | Alt Text Pattern |
|---|---|
| Game thumbnail (grid) | "{Game Title} — free online {primary category} game" |
| Game icon (detail page) | "{Game Title} game icon" |
| Category icon | "{Category Name} games" |
| Author avatar (future) | "{Author Name} — game developer" |
Image Optimization
| Rule | Detail |
|---|---|
| Format | WebP primary, JPEG fallback |
| Sizes | Thumbnails served at 256px, 512px (responsive via srcset) |
| Compression | Quality 80 for thumbnails (visually lossless) |
| CDN | All images served through CDN with cache headers |
| Filenames | Use slugs: subway-surfers-thumb.webp not abc123.webp |
Crawl Budget
With thousands of game pages, efficient crawl budget usage matters.
| Strategy | Implementation |
|---|---|
| Sitemap priority | Higher-ranked games get higher priority in sitemap |
| Block low-value pages | API routes, admin pages blocked in robots.txt |
| Server response speed | Target < 200ms TTFB for all public pages |
| Avoid soft 404s | Hidden/Draft games return proper 404, not an empty page |
| Clean URL params | Use Google Search Console to indicate which params to ignore |
| No orphan pages | Every visible game must be reachable from at least one category or the Home page |
International SEO (Future)
Not in MVP. When multi-language support is added:
| Step | Detail |
|---|---|
| URL structure | Subdirectory: playupi.com/fr/games/..., playupi.com/es/games/... |
| hreflang tags | <link rel="alternate" hreflang="fr" href="https://playupi.com/fr/games/tetris" /> |
| Default language | English (hreflang="en") with x-default fallback |
| Translated content | Game titles and descriptions translated. Slugs stay in English for URL stability |
| Sitemap | Separate sitemap per language, or multi-language sitemap with hreflang |
See Decisions → Open Questions for i18n status.
Search Console Setup
After launch, configure Google Search Console:
- Verify ownership via DNS TXT record or HTML meta tag
- Submit sitemap at
https://playupi.com/sitemap.xml - Monitor coverage report for crawl errors, excluded pages, indexing issues
- Check Core Web Vitals report for performance regressions
- Review search performance (queries, clicks, impressions, position) weekly
Key Reports to Watch
| Report | What to Look For |
|---|---|
| Coverage | Increasing indexed pages as new games are added. Zero “Excluded — server error” |
| Performance | Click-through rate by page type. Brand vs non-brand query split |
| Core Web Vitals | All pages in “Good” status. Flag any regressions |
| Sitemaps | All submitted URLs discovered and indexed |
| Removals | No unexpected pages flagged for removal |