Here are some pitfalls and lessons learned with Next.js and Vercel.
"TLDR: This article provides an in-depth exploration of deploying and optimizing Next.js on the Vercel platform, detailing the three rendering methods—SSR, ISR, and SSG—along with their implementation mechanisms. It compares and analyzes the application differences between `cache()` and `fetch()` in various scenarios, and proposes solutions to the challenges of using databases in Serverless environments."
Background
When deploying projects on Vercel, I've occasionally encountered errors but never paid much attention to them. Additionally, I haven't deeply understood the differences between serverless and regular deployment, nor some of Next.js's optimizations for serverless environments.
Next.js Rendering Methods
- SSR: Fully server-side rendering. Each request spins up an instance, queries the database once, fetches data once, and renders the page once. Suitable for scenarios requiring real-time data.
- ISR: This is actually static content placed on a CDN, but it gets re-rendered periodically and then pushed back to the CDN. So essentially it's static and doesn't involve the server. Suitable for scenarios where real-time data isn't critical but speed is a priority, such as blogs.
- SSG: This is the easiest to understand—completely static, just deployed to a CDN.
How Next.js Implements These Methods:
- SSR:
getServerSideProps - ISR:
getStaticPropswith an addedrevalidateparameter, indicating recompilation and periodic deployment to the CDN - SSG:
getStaticPropswithout therevalidateparameter—completely static, so unless recompiled, it never changes
Understanding cache() and fetch()
In Next.js, these should be understood from an "instance-level" runtime perspective (especially in Serverless scenarios).
cache()
-
Cache scope is limited to a single Node.js instance
cache()caches the result of a function execution, and it's only valid within the current Next.js instance. In other words, if you call the function multiple times within one instance, the results are cached to avoid redundant computation.However, it's important to note:
- In multi-instance deployments (such as Serverless or Vercel), memory is not shared between instances, so caches are isolated;
- Once an instance is destroyed (e.g., Vercel's Serverless cold start), the cache is lost as well.
-
Behavior when used with ISR
cache()can be used during the ISR build phase to cache certain repeatedly executed functions, improving build efficiency. After the build completes, static pages are uploaded to the CDN, and subsequent requests hit the CDN directly—these functions orcache()will never be executed again. -
Role when used with SSR
In SSR scenarios,
cache()can reduce repeated function calls during page rendering, improving server response efficiency. However, it cannot share cache across requests, lacks incremental update capabilities, and cannot replace ISR.More importantly, in Serverless environments, due to short instance lifecycles and statelessness,
cache()caching cannot persist and has almost no practical significance.
SSR Risks in Serverless Environments
In Serverless architectures (such as Vercel's default deployment):
- Each request may correspond to a brand-new instance (cold start);
- Long-lived resources like database connection pools cannot be reused;
- Rapidly refreshing pages multiple times may cause frequent database connection creation, exhausting resources or even crashing.
Therefore, if you need to use SSR with a database dependency, it's recommended to:
- Use connection pool proxies like Prisma Data Proxy or PgBouncer;
- Avoid frequent database access with SSR, and prioritize static generation or caching mechanisms.
How Serverless Uses Databases
In Serverless architectures, if you need to persist cloud data, common approaches are:
- Small projects: Use Upstash Redis (supports HTTP requests, natively suited for Serverless)
- Large projects: Use Supabase or Prisma + database connection pool proxies (such as PgBouncer)
Traditional databases use TCP long connections and rely on connection pools. In Serverless, because functions are stateless with short lifecycles, connection pools are difficult to reuse, which can easily exhaust connection limits. Supabase optimizes connection reuse through PostgREST + PgBouncer, but this doesn't completely solve the problem.
The benefit is that middleware can reduce overhead like authentication and transaction initialization for each connection. But essentially, the connection pressure is just shifted from the database to the space between cloud functions and the connection pool proxy—this part still uses short connections and cannot be avoided.
export const dynamic = "force-static";
It's worth noting that I previously didn't like using fetch to retrieve data, preferring to wrap Prisma in functions instead. However, Next.js cannot recognize this approach as ISR—it can with fetch. If you must use Prisma and still want ISR, you can manually add export const dynamic = "force-static"; to tell Next.js this is a static page, and then add export const revalidation = 3600 to implement ISR.
My knowledge is limited and I haven't deeply studied serverless—these are just key points I've summarized from occasionally freeloading off Vercel.