Skip to content

Instantly share code, notes, and snippets.

@robinebers
Created November 3, 2025 08:01
Show Gist options
  • Select an option

  • Save robinebers/b28d750bd6bf2c46b915820f1b888b08 to your computer and use it in GitHub Desktop.

Select an option

Save robinebers/b28d750bd6bf2c46b915820f1b888b08 to your computer and use it in GitHub Desktop.

Implement Vercel AI Gateway + Nano Banana City Quiz

This is a Next.js 16 app installed into test-app which you now need to modify into a browser-based game.

What we’ll integrate

  • Use AI Gateway single model strings with the AI SDK:
    • Image (Nano Banana): google/gemini-2.5-flash-image
    • Text (optional utility): google/gemini-2.5-flash
  • Auth via AI_GATEWAY_API_KEY in .env.local.

Dependencies (bun)

  • Install core SDK and gateway provider: bun add ai @ai-sdk/gateway

Environment

  • .env.local (not committed):
    • AI_GATEWAY_API_KEY=...
    • GAME_SECRET=... (32+ char random string used for HMAC/encryption)

Server: model calls and secure answer tokens

  • File lib/ai.ts: export helpers for models using AI Gateway.

    • Minimal image generation call (AI SDK):
      import { generateText } from 'ai';
      import { gateway } from '@ai-sdk/gateway';
      
      const model = gateway('google/gemini-2.5-flash-image');
      
      const result = await generateText({
        model,
        providerOptions: {
          google: {
            responseModalities: ['IMAGE'],
            imageConfig: { aspectRatio: '1:1' }
          }
        },
        prompt: '8-bit pixel art city scene ...'
      });
      // image in result.files[0]
      
      • Must include NO text, flags, or hints on the image other than landmarks and skyline
  • File lib/capitals.ts: 25 capital names and optional landmark hint strings.

  • File lib/crypto.ts: HMAC-SHA256 and AES-256-GCM helpers.

    • Token design (no DB): server returns { nonce, signature, iv, ciphertext, tag }.
    • signature = HMAC(secret, ${nonce}:${normalizedCity})
    • ciphertext = AES-GCM encrypt of city (revealed only on answer check).
  • API route app/api/new-round/route.ts (Node runtime):

    • Picks a correct city and two random distractors, shuffles choices.
    • Calls Nano Banana to generate a square 8‑bit scene with a landmark.
    • Returns JSON: { imageBase64, mimeType, choices, nonce, signature, iv, tag }.
  • API route app/api/check-answer/route.ts (Node runtime):

    • Validates { guess, nonce, signature, iv, tag } via HMAC.
    • Decrypts ciphertext to get correctCity and compares to guess.
    • Responds { correct: boolean, correctCity?: string } (include correctCity only if wrong or for confirmation).

UI/UX (Next.js App Router)

  • Header: app/layout.tsx stable header (no CLS)
  • Page: app/page.tsx implements the quiz flow:
    • Centered "Let’s Play" button
    • On click: show loading indicator/skeleton while image generates.
    • Display the generated image (square, aspect-square, max width), then render 3 radio options and “Submit my answer”. Hide the city name until after submission.
    • After submit: brief toast/message for right/wrong; if wrong, show the correct city; then show "Play again" CTA which re-triggers a new round.
  • Accessibility: proper role, aria-live for result message, keyboard-nav for radio group, alt text without revealing answer.
  • No localStorage usage; all state in memory. Avoid exposing the correct city in DOM/props or generated image until after server validation.

Styling & Responsiveness

  • mobile-first layout.
  • Image container with reserved space to avoid layout shift; skeleton while loading.

Notes

  • You must install Next.js with the command above
  • If image gen fails, we’ll show a friendly retry with exponential backoff (client) and return an error JSON from the API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment