Sudden traffic spike caused many users to bounce off the website

Hey, the website I made was showcased on a twitch stream, which caused a sudden traffic spike of approximately 200-400 users. I’m on a hobby plan, and I treat this project as a learning playground to figure out all things vercel. The logs show errors seen in the image below, I can’t find any information on the topic and would love to find out more, as I’d love to prevent that in the future - if possible.

Some things that came to my mind are that the page on initial load performs 6 fetches (100 users each) to the twitch.tv API to grab viewer count info. Perhaps it’s hitting a rate limit, but I can’t know for sure - couldn’t reproduce it locally.

If I understood and implemented caching correctly, it shouldn’t be an issue as I want to keep the response in cache for 5 minutes, but perhaps I’m not understanding something very simple here - I’m a newbie to the more ‘backend’ parts of the framework.

I’ll attach the /streamers route contents below:

import db from '@/lib/db';
import { streamers } from '@/lib/schema';
import { NextResponse } from 'next/server';
import { getTwitchToken } from '../utils';

async function validateToken(token: string) {
  const response = await fetch('https://id.twitch.tv/oauth2/validate', {
    headers: {
      Authorization: `OAuth ${token}`,
    },
  });

  if (!response.ok) {
    console.error('Token validation failed:', await response.text());
    return false;
  }

  return true;
}

async function fetchStreamerBatch(usernames: string[], initialToken: string) {
  let token = initialToken;
  const isValid = await validateToken(token);
  if (!isValid) {
    console.log('Token invalid, attempting to get new token...');
    token = await getTwitchToken();
    if (!token) {
      throw new Error(
        'Failed to get new Twitch token after validation failure'
      );
    }
  }

  const queryString = usernames.map((name) => `user_login=${name}`).join('&');

  console.log(
    `Attempting Twitch API call with token: ${token.substring(0, 10)}...`
  );

  const response = await fetch(
    `https://api.twitch.tv/helix/streams?${queryString}`,
    {
      headers: {
        'Client-ID': process.env.TWITCH_CLIENT_ID!,
        Authorization: `Bearer ${token}`,
      },
      cache: 'no-store',
    }
  );

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(
      `Twitch API error: ${response.status} - ${errorText}. Client ID: ${process.env.TWITCH_CLIENT_ID?.substring(0, 5)}...`
    );
  }

  return (await response.json()).data;
}

export async function GET(request: Request) {
  try {
    const [allStreamers, token] = await Promise.all([
      db
        .select({
          id: streamers.id,
          name: streamers.name,
          twitchUrl: streamers.twitchUrl,
          isSweat: streamers.isSweat,
          avatarUrl: streamers.avatarUrl,
        })
        .from(streamers),
      getTwitchToken(),
    ]);

    if (!token) {
      console.error('Token retrieval failed');
      throw new Error(
        'Failed to get Twitch token - token is null or undefined'
      );
    }

    const usernames = allStreamers
      .map((streamer) => streamer.twitchUrl?.split('/').pop())
      .filter((username): username is string => !!username);

    const OPTIMIZED_BATCH_SIZE = 100;
    const batchPromises = Array.from(
      { length: Math.ceil(usernames.length / OPTIMIZED_BATCH_SIZE) },
      (_, i) => {
        const start = i * OPTIMIZED_BATCH_SIZE;
        const batchUsernames = usernames.slice(
          start,
          start + OPTIMIZED_BATCH_SIZE
        );
        return fetchStreamerBatch(batchUsernames, token);
      }
    );

    const streamDataArrays = await Promise.all(batchPromises);
    const streamData = streamDataArrays.flat();

    const streamDataMap = new Map(
      streamData.map((stream) => [stream.user_login.toLowerCase(), stream])
    );

    const liveStreamers = allStreamers
      .map((streamer) => {
        if (!streamer.twitchUrl) {
          return null;
        }

        const twitchUsername = streamer.twitchUrl
          .split('/')
          .pop()
          ?.toLowerCase();
        if (!twitchUsername) {
          return null;
        }

        const twitchData = streamDataMap.get(twitchUsername);
        if (!twitchData) {
          return null;
        }

        return {
          id: streamer.id,
          name: streamer.name,
          twitchUrl: streamer.twitchUrl,
          isSweat: streamer.isSweat,
          avatarUrl: streamer.avatarUrl,
          viewerCount: twitchData.viewer_count,
          live: twitchData.type === 'live',
          gameName: twitchData.game_name,
        };
      })
      .filter(
        (streamer): streamer is NonNullable<typeof streamer> =>
          streamer !== null
      );

    return NextResponse.json(liveStreamers, {
      headers: {
        'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=60',
      },
    });
  } catch (error) {
    console.error('Detailed error in streamer fetch:', error);
    return NextResponse.json(
      {
        error:
          error instanceof Error
            ? error.message
            : 'Failed to fetch streamer status',
      },
      { status: 500 }
    );
  }
}

Thanks for any help in advance!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.