Cost Optimization

Cost Optimization Tips for Nextjs project deployed on vercel

Context

When I launched the first version of Skiper UI in January 2025, I was thrilled with its early success. But soon, I noticed a problem: costs were skyrocketing. Edge requests, server usage, and image loads were maxed out. I assumed it was due to high traffic, but with only one to two thousand daily visitors, the real issue was unoptimized resources.

Vercel Metrics

Here are the key metrics Vercel plan has:
  • Edge Requests
  • Fast Data Transfer
  • Image Optimization (Cache Reads + Transformations)
  • Web Analytics Events
  • Fast Origin Transfer

Fast Data Transfer

Fast Data Transfer refers to the data moved from Vercel to your users. The larger your public folder, the higher the cost. Every time a user visits your page and some asset gets loaded, like an image or video, it adds up.
Simple math: If 1,000 users visit and you have a 10MB video, that's 10GB of data transfer. And the limit is just 100GB/month.

Edge Requests

Edge requests are HTTP requests made to your site via Vercel's edge network. Every page visit, link click, or asset load (like fonts or images) counts as one. With only 2,000 daily users, my edge requests were still clogging up resources. And the limit is just 1 million/month. This is extreme—you should use them carefully.

Fast Origin Transfer

It measures the amount of dynamic data transferred from your origin (serverless functions, edge functions, etc.) to the Vercel Edge Network. Fast Origin Transfer = Edge Requests - Static Assets (only dynamic content). 10GB limit, but if your app doesn't have file uploads or large dynamic responses, you'll stay well under the limit.

Web Analytics Events

Vercel’s web analytics aren’t enabled by default, but if you turn them on, they track user behavior. This sounds great for small projects, but as your project grows, it becomes a problem:
  • Each event triggers an edge request.
  • The initial plan includes only 50,000 events.
  • With 400,000 events, you’d add 400,000 edge requests, significantly increasing costs.

Fix 01: Next.js Image Tag

Skip Next.js’s <Image> tag—seriously. It throws warnings to use it for auto-optimization, doing fancy stuff like resizing, compressing, and converting to WebP or AVIF to cut file sizes and boost load times. Sounds cool, but it jacks up costs with heavy transformations. Instead, I use ffmpeg to manually compress images for better control and savings. Stick to the <img> tag and handle it yourself. Use <Image> only for must-have cases, but don’t overdo it.
ffmpeg -i input.png -c:v libwebp -lossless 1 -q:v 100 -preset picture -an -metadata title= -metadata comment= output.webp
# copy all images to a folder open the terminal there and run
for f in *.png; do
  ffmpeg -i "$f" -c:v libwebp -q:v 75 -lossless 0 -preset picture -an -metadata title= -metadata comment= "${f%.png}.webp"
done

Fix 02 Data Transfer

To optimize data transfer, don't store your entire public folder in Vercel—it bloats costs. Instead, I use an S3 bucket and CloudFront for CDN, and create a script that will upload all the assets to S3. I just do npx run s3:upload and it uploads all to S3. A custom function retrieves a CDN URL for each image, updating my app with CloudFront addresses for faster, cheaper delivery. you can't imagine the difference bw prices
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";

// Auto-upload to S3 during build
const uploadToS3 = async (filePath: string) => {
  const s3Client = new S3Client({
    region: 'your-region',
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
  });
  
  const fileContent = fs.readFileSync(filePath);

  const command = new PutObjectCommand({
    Bucket: 'your-bucket',
    Key: `images/${path.basename(filePath)}`,
    Body: fileContent,
    ContentType: 'image/jpeg'
  });

  return s3Client.send(command);
};

Fix 03: Edge Request Optimization

For edge requests, avoid heavy web analytics and event tracking. Vercel's 1 million edge request limit sounds generous, but every page visit, font load, or image fetch counts. So if you're expecting more traffic, there's no need to use analytics from Vercel. You could use it, but it doesn't make any sense to trigger the unnecessary edge requests just for the analytics. I'd prefer something like Plausible for that—it's really dirt cheap and has unlimited limits.
pnpm i next-plausible
// app/layout.js
import PlausibleProvider from 'next-plausible'

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <PlausibleProvider domain="example.com" />
      </head>
      <body>{children}</body>
    </html>
  )
}

Last Note

I'm not a super optimization expert, but when things went wrong, I found some solutions that worked for me, and I hope they help you too.
Go Back