Complete Guide to Astro SSR: Enable Server-Side Rendering in 3 Steps and End Your Tech Stack Confusion

Introduction
Ever experienced this? Your Astro blog runs blazingly fast with a Lighthouse score of 95+, and just as you’re feeling proud, your boss says, “Let’s add user login functionality.” Suddenly, you’re stuck. How do you implement user login on a static site? You dive into the official documentation, and a flood of concepts like SSR, SSG, Hybrid, and adapters hits you, making your head spin.
To be honest, that’s exactly how I felt when I first encountered Astro SSR. Astro’s selling point is speed, so won’t adding server-side rendering make it slow? With so many adapters like Vercel, Netlify, and Node.js, which one should you choose? What do those output and prerender settings in the config file actually mean?
Actually, configuring Astro SSR isn’t as complicated as you think. In this article, I’ll explain in the most straightforward way: when you absolutely need SSR (instead of sticking with SSG), how to quickly configure various adapters, and how to use SSR and SSG simultaneously in one project (Hybrid mode). After reading this, you’ll be able to independently determine whether your project needs SSR and configure it within 30 minutes.
Chapter 1: SSR Fundamentals and Tech Stack Selection
When Do You Need SSR Instead of SSG?
Let’s start with the simplest criterion: Is your content determined at build time, or does it potentially change with every request?
SSG (Static Site Generation) is like a restaurant’s pre-made set meals. The chef prepares everything in the morning, and when customers arrive, the food is served immediately鈥攕uper fast. Blog posts, product pages, and “About Us” sections鈥攃ontent that rarely changes鈥攁re perfect for SSG.
SSR (Server-Side Rendering) is like cooking to order. After the customer places an order, the chef prepares the dish based on your requirements right then. The “Welcome back, John” message a user sees after logging in, real-time stock prices, the number of items in a shopping cart鈥攖hese vary for each person and must use SSR.
You might wonder, does my project actually need SSR? If any of these 5 scenarios apply to you, you should consider SSR:
1. User Authentication and Personalized Content
The most typical example is login. You can’t know at build time who will log in or what username to display. For instance, in a learning platform I built, the homepage needed to show “Continue Learning: Lesson 5,” which required SSR to dynamically generate content based on the logged-in user’s progress.
2. Real-Time Data Display
Weather forecasts, stock quotes, sports scores. This data changes every minute鈥攜ou can’t rebuild your website every minute, right? With SSR, you fetch the latest data every time a user visits.
3. Database Queries
E-commerce product search鈥攅ach keyword yields different results, and you can’t pre-generate every possible search result page. With SSR, you query the database in real-time when users search and return results.
4. API Routes
Form submissions, file uploads, third-party API calls鈥攁ll require backend logic. Astro’s SSR mode supports creating API routes (src/pages/api/xxx.js), so you don’t need a separate backend server.
5. A/B Testing and Personalized Recommendations
Displaying different content based on user location, visit time, or browsing history. For example, Taobao’s homepage shows different recommended products for each user鈥攖his kind of personalization requires SSR.
At this point, someone might ask: “Can I use SSR for blog post detail pages?” You can, but there’s no need to. Article content is fixed鈥擲SG generates static HTML that’s served directly from a CDN, resulting in faster access and lower server costs. SSR isn’t a silver bullet; don’t use it just for the sake of using it.
Hybrid Mode: The Best of Both Worlds
Astro 2.0’s Hybrid mode is pretty clever鈥攊t lets you use SSG for static pages and SSR for dynamic pages in the same project. For example, in an e-commerce site:
- Homepage, About page, Help docs 鈫?SSG (fast loading)
- Login page, User dashboard, Shopping cart 鈫?SSR (dynamic content)
- Product detail pages 鈫?SSG (fixed content)
- Search results pages 鈫?SSR (real-time queries)
This setup keeps static pages blazingly fast while perfectly implementing dynamic features. A friend’s blog uses this approach鈥攁rticle lists and details use SSG, while the comment section uses SSR, and the Lighthouse score still maintains 95+.
Chapter 2: Quick Start - Enable SSR Mode in 3 Steps
Configuring Astro SSR from Scratch (Node.js Adapter)
Alright, once you’ve determined your project needs SSR, let’s start configuring. I’ll demonstrate with the Node.js adapter first鈥攊t’s the most universal solution, suitable for self-hosted servers or VPS deployment.
Step 1: One-Command Adapter Installation
Astro provides a super simple automatic configuration command. Just run this in your project root:
npx astro add nodeThis single command automatically does three things:
- Installs the
@astrojs/nodepackage - Modifies the
astro.config.mjsconfiguration file - Updates dependencies in
package.json
After running the command, you’ll see a bunch of green checkmarks in the terminal, indicating successful configuration. If you want to install manually (for instance, to specify a version), you can also do this:
npm install @astrojs/nodeThen manually modify the config file (covered in the next step).
Step 2: Modify Configuration File
Open astro.config.mjs in your project root. If you used the automatic configuration command, you’ll already see this content:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // Enable SSR mode
adapter: node({
mode: 'standalone' // Standalone server mode
}),
});Let me highlight these two configuration options:
output configuration:
'static'(default): All pages use SSG, outputs pure static HTML'server': All pages use SSR, dynamically generated on each request'hybrid': Default SSG, can enable SSR per page (recommended!)
mode configuration:
'standalone': Astro starts an independent Node.js server, suitable for direct deployment'middleware': Generates middleware, can integrate into Express, Koa, and other frameworks
I usually use standalone because Astro’s built-in server is sufficient鈥攏o need for additional integration. If your project already has an Express backend and you want Astro as part of it, use middleware.
Step 3: Build and Run
After configuration, build the project:
npm run buildAfter building, you’ll find a server/ folder in the dist/ directory with an entry.mjs file鈥攖his is the SSR server’s entry point.
Run the SSR server:
node ./dist/server/entry.mjsBy default, it starts at http://localhost:4321. Visit your site, and all pages are now SSR!
Development Environment Debugging
During development, you don’t need to build every time. Just use:
npm run devThe dev server automatically supports SSR with real-time code changes鈥攕uper convenient.
Common Troubleshooting
Port in use: If port 4321 is occupied, set an environment variable:
PORT=3000 node ./dist/server/entry.mjsAdapter module not found: Confirm
@astrojs/nodeis installed, runnpm installto reinstall dependenciesPage 404: Check if files in the
src/pages/directory are correct鈥擲SR mode still follows Astro’s routing rules
Honestly, configuring SSR is really this simple. My first time, from start to running successfully, took less than 5 minutes. If you’re deploying to Vercel or Netlify, there are dedicated adapters with even simpler configuration鈥擨’ll cover that in detail in the next chapter.
Chapter 3: Detailed Configuration of Mainstream Adapters
How to Choose Between Vercel, Netlify, and Cloudflare?
If your project is hosted on Vercel, Netlify, or Cloudflare, congratulations鈥攃onfiguring SSR will be even easier. These platforms have officially maintained Astro adapters with zero-config deployment.
Vercel Adapter - The King of Serverless Functions
Vercel is my most-used deployment platform. The free tier is sufficient for personal projects, and configuration is super simple:
npx astro add vercelThis command automatically configures everything. The config file looks like this:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel(),
});Vercel’s Special Feature: ISR (Incremental Static Regeneration)
This is a Vercel-exclusive feature that makes your SSR pages as fast as SSG. Simply put, the first visit generates the page with SSR, then caches it for a period. Subsequent visits use the cache, and it regenerates when expired.
adapter: vercel({
isr: {
expiration: 60, // Cache for 60 seconds
},
}),For example, on a news site, article detail pages updating once per minute is sufficient鈥攏o need to query the database on every request. With ISR, you get SSR’s flexibility and SSG’s speed.
Vercel Deployment Process:
- Configure the adapter
- Push code to GitHub
- Import project in Vercel dashboard
- Build command:
npm run build(auto-detected) - Click deploy, done!
Netlify Adapter - Master of Edge Functions
Netlify is also a popular deployment platform, especially suitable for static sites with dynamic features.
npx astro add netlifyConfig file:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';
export default defineConfig({
output: 'server',
adapter: netlify({
edgeMiddleware: true, // Enable Edge middleware
}),
});What is edgeMiddleware?
Simply put, it runs middleware logic (like authentication, redirects) on edge nodes for faster responses. If your site has location-based features (like displaying different languages based on user location), edge is very useful.
Netlify Redirect Configuration
One convenient aspect of Netlify is automatic redirect handling. For example, if you want to redirect /old-page to /new-page, just create a _redirects file in your project root:
/old-page /new-page 301It takes effect automatically after deployment, no code changes needed.
Cloudflare Adapter - Global CDN Acceleration
If your users are spread across the globe, Cloudflare is the best choice. Its Workers run in 300+ data centers worldwide with extremely low latency.
npx astro add cloudflareConfig file:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'server',
adapter: cloudflare(),
});Cloudflare Limitations
Note that Cloudflare Workers’ runtime environment isn’t completely identical to Node.js鈥攕ome Node.js APIs won’t work (like fs file system). If your project depends on these APIs, Cloudflare might not be suitable.
Adapter Comparison Table
| Adapter | Use Case | Core Advantage | Main Limitation |
|---|---|---|---|
| Node.js | Self-hosted server, VPS | Full control, no restrictions | Requires self-management, higher cost |
| Vercel | Personal projects, small teams | Zero-config, ISR support | Free tier limits (100GB bandwidth/month) |
| Netlify | Static + dynamic features | Fast Edge Functions | Build time limit (300 minutes/month free) |
| Cloudflare | Global users, low latency | Edge computing, low price | Workers environment restrictions, some Node APIs unavailable |
My Selection Recommendations:
- Blog, documentation sites: Prioritize Vercel or Netlify鈥攆ree tier sufficient, easy deployment
- E-commerce, SaaS apps: Vercel (ISR is great), or self-hosted Node.js server (full control)
- International products: Cloudflare (global acceleration)
- Enterprise projects: Self-hosted Node.js (data privacy, full control)
There’s no absolute standard for which to choose鈥攊t depends on your project needs and budget. I use Vercel for my personal blog and self-hosted servers for client enterprise sites鈥攂oth work great.
Chapter 4: Hybrid Mixed Rendering in Practice
Using SSR and SSG Simultaneously in One Project
Alright, we’ve covered pure SSR configuration. Now for the key point: Hybrid mode. This is Astro’s killer feature, letting you enjoy SSG’s speed and SSR’s flexibility in the same project.
Configuring Hybrid Mode
Just change output to 'hybrid':
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'hybrid', // Default SSG, SSR on demand
adapter: node(),
});After configuration, all pages default to SSG, then you can add a single line of code to pages that need SSR to enable it.
Page-Level Rendering Control
Here’s the key鈥攈ow do you make a specific page use SSR? Add one line to the page file’s frontmatter:
// src/pages/dashboard.astro (SSR)
---
export const prerender = false; // Disable prerendering, use SSR
const user = Astro.cookies.get('user');
---
<h1>Welcome back, {user?.name}</h1>
<p>You have {user?.notifications} unread messages</p>That’s it! prerender = false means “don’t generate at build time, generate dynamically when users visit.”
Conversely, if you set output to 'server' (all SSR) and want a specific page to use SSG, do this:
// src/pages/about.astro (SSG)
---
export const prerender = true; // Force generation at build time
---
<h1>About Us</h1>
<p>This page's content doesn't change鈥攑re-generated for ultra-fast access.</p>Key Summary (Don’t Get Confused):
| output config | Default behavior | How to change individual pages |
|---|---|---|
'hybrid' | All pages SSG | export const prerender = false 鈫?that page uses SSR |
'server' | All pages SSR | export const prerender = true 鈫?that page uses SSG |
I used to get this backwards all the time. Then I remembered: hybrid prioritizes SSG, server prioritizes SSR.
Real-World Case: Blog + User System
Suppose you’re building a blog platform with article display and user login functionality. The ideal configuration:
Project Structure:
src/pages/
鈹溾攢鈹€ index.astro // Homepage (SSG)
鈹溾攢鈹€ about.astro // About page (SSG)
鈹溾攢鈹€ blog/
鈹? 鈹溾攢鈹€ [slug].astro // Article detail (SSG)
鈹? 鈹斺攢鈹€ index.astro // Article list (SSG)
鈹溾攢鈹€ login.astro // Login page (SSR)
鈹溾攢鈹€ dashboard.astro // User dashboard (SSR)
鈹斺攢鈹€ api/
鈹斺攢鈹€ comments.js // Comments API (SSR)Configuration File:
// astro.config.mjs
export default defineConfig({
output: 'hybrid', // Default SSG
adapter: vercel(), // Deploy to Vercel
});Static Pages (No Special Configuration Needed):
// src/pages/blog/[slug].astro
---
// No prerender setting, defaults to SSG
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<article>
<h1>{post.data.title}</h1>
<div set:html={post.body} />
</article>Dynamic Pages (Require SSR):
// src/pages/dashboard.astro
---
export const prerender = false; // Enable SSR
// Check user login status
const token = Astro.cookies.get('token')?.value;
if (!token) {
return Astro.redirect('/login');
}
// Fetch user info from database
const user = await fetch(`https://api.example.com/user`, {
headers: { Authorization: `Bearer ${token}` }
}).then(res => res.json());
---
<div>
<h1>Welcome, {user.name}</h1>
<p>Email: {user.email}</p>
<p>Last login: {user.lastLogin}</p>
</div>API Routes (Automatically SSR):
// src/pages/api/comments.js
export async function POST({ request }) {
const { articleId, content } = await request.json();
// Save comment to database
await db.comments.insert({
articleId,
content,
createdAt: new Date(),
});
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
export async function GET({ url }) {
const articleId = url.searchParams.get('articleId');
// Read comments from database
const comments = await db.comments.findMany({
where: { articleId },
orderBy: { createdAt: 'desc' }
});
return new Response(JSON.stringify(comments), {
headers: { 'Content-Type': 'application/json' }
});
}Benefits of This Configuration:
- Static pages (articles, homepage) remain lightning fast, Lighthouse score 95+, all served from CDN
- Dynamic pages (user dashboard) fetch data in real-time, each user sees different content
- API routes provide backend capabilities, no need for a separate backend server
- Short build time, only static pages need prerendering, dynamic pages don’t count toward build time
In a project I worked on鈥?0 blog posts plus a user system鈥攂uild time was only 20 seconds. After deployment, static pages loaded instantly, and dynamic pages responded in under 100ms. Hybrid mode really is the best practice.
Chapter 5: Common Issues and Best Practices
Pitfalls in SSR Configuration and Solutions
During SSR configuration, I’ve stepped on plenty of landmines. Here’s a compilation of common issues and solutions to help you avoid them.
Issue 1: Error Astro.clientAddress is only available when using output: 'server'
Cause: You’re using Astro.clientAddress (to get user IP) in your code, but output in the config file is still 'static'.
Solution:
// astro.config.mjs
export default defineConfig({
output: 'server', // or 'hybrid'
adapter: node(),
});Dynamic APIs like Astro.clientAddress, Astro.cookies, and Astro.redirect() only work in SSR mode.
Issue 2: Page 404 After Deployment, Works Fine Locally
Cause: Adapter misconfigured, or deployment platform’s build command/output directory settings are wrong.
Solution:
Vercel Deployment:
- Build command:
npm run build - Output directory:
.vercel/output(automatic) - Ensure you don’t manually configure routes in
vercel.json鈥攍et Astro handle it
Netlify Deployment:
- Build command:
npm run build - Publish directory:
dist(for static) or.netlify(for SSR) - If still 404, check
netlify.toml:[build] command = "npm run build" publish = "dist"
Issue 3: SSR Pages Load Very Slowly, Over 2 Seconds
Cause: Insufficient server performance, or database queries too slow.
Solution:
Use caching:
// src/pages/api/news.js export async function GET() { const cached = await redis.get('news'); if (cached) { return new Response(cached, { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' // Cache for 60 seconds } }); } const news = await fetchNewsFromDB(); await redis.set('news', JSON.stringify(news), 'EX', 60); return new Response(JSON.stringify(news), { headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=60' } }); }Optimize database queries:
- Add indexes
- Reduce JOINs
- Only query needed fields
Consider ISR (if using Vercel):
adapter: vercel({ isr: { expiration: 300 } // Cache for 5 minutes }),
Issue 4: Can’t Access Environment Variables on Client Side
Cause: Astro’s environment variables are split between client and server.
Solution:
Server-side use (SSR pages, API routes):
const secret = import.meta.env.SECRET_KEY; // Any environment variable worksClient-side use (JavaScript in the browser):
const apiUrl = import.meta.env.PUBLIC_API_URL; // Must start with PUBLIC_.env file configuration:
SECRET_KEY=abc123 # Server-side only
PUBLIC_API_URL=https://api.example.com # Both client and serverIssue 5: adapter.setApp is not a function Error
Cause: Incompatible Astro and adapter versions.
Solution:
# Update to latest versions
npm update astro @astrojs/node
# Or specify compatible versions (check official docs)
npm install astro@latest @astrojs/node@latestGenerally, keeping both Astro and the adapter on the latest versions avoids issues.
Best Practices Summary
- Default to Hybrid mode: Unless all pages need SSR,
output: 'hybrid'is the optimal choice - Enable SSR on demand: Only set
prerender = falsefor pages that truly need dynamic rendering - Static assets via CDN: Put images, CSS, JS files in the
public/directory鈥攖hey’ll automatically use CDN, not SSR - Caching strategy: For dynamic content that doesn’t change often (like news lists), use caching or ISR to reduce server load
- Separate environment variables: Use server-side environment variables for sensitive info,
PUBLIC_prefix for public configs - Monitor performance: Use Vercel Analytics or Google Analytics to monitor SSR page response times and optimize promptly
Conclusion
After all that, it really boils down to three key points:
1. SSR isn’t a silver bullet鈥攗se it where it makes sense
Don’t get excited just because you see SSR, and don’t think SSG is outdated. Use SSG for static pages, SSR for dynamic ones, and Hybrid mode for most projects. I’ve seen people convert an entire blog to SSR, only to see performance decline鈥攁fter all, blog content doesn’t change, so SSG via CDN is definitely faster.
2. Choose adapters based on deployment platform鈥攃onfiguration is actually simple
If you’re using Vercel/Netlify/Cloudflare, one line npx astro add [platform] and you’re done. If self-hosting, npx astro add node takes about 5 minutes. Don’t be intimidated by the documentation鈥攊t’s much simpler in practice than it looks.
3. Hybrid mode gives you the best of both worlds
This is Astro’s essence. Static pages maintain 95+ Lighthouse scores, dynamic pages enable personalized features, build time doesn’t increase, and server costs don’t explode. When I start projects now, Hybrid mode is my first choice.
Next Steps
After reading this article, you can:
- Try it right now: Open your Astro project, run
npx astro add node, and experience SSR in 5 minutes - Think about your needs: List which pages in your project need dynamic rendering and which can stay static
- Dive deeper: Astro’s recently released Server Islands feature lets you embed SSR components in SSG pages for even more flexibility
By the way, if you encounter issues during configuration, head to Astro’s official Discord community to ask questions鈥攔esponses are super fast, and the community vibe is great.
One final reminder: don’t over-optimize. If your site doesn’t get much traffic (daily PV < 10,000), a static site is probably sufficient鈥攏o need to add SSR complexity. Tech choices should serve the business, not be used for their own sake.
Best of luck with your configuration, and feel free to leave comments with any questions!
FAQ
When do I need Astro SSR instead of SSG?
• User authentication/personalized content
• Real-time data
• Database queries
• API endpoints
• Dynamic routing
Use SSG for:
• Blogs, documentation, static content - faster and simpler
How do I enable SSR in Astro?
1) Install adapter (npx astro add vercel/netlify/node)
2) Set output mode in astro.config.mjs (output: 'server' or 'hybrid')
3) Set prerender = false for pages needing SSR
What's the difference between SSR, SSG, and Hybrid mode?
SSR: rendered on server per request
Hybrid: mix of both - static by default, SSR where needed
Hybrid is recommended for most projects.
Which adapter should I choose?
• Vercel adapter for Vercel
• Netlify adapter for Netlify
• Node.js adapter for custom servers or Docker
Each adapter is optimized for its platform.
Does SSR make Astro slower?
Use Hybrid mode to keep static pages fast (SSG) while enabling SSR only where needed.
Performance impact is minimal when used correctly.
How do I handle environment variables in SSR?
Client-side variables must have PUBLIC_ prefix.
Use .env files:
• SECRET_KEY (server-only)
• PUBLIC_API_URL (both client and server)
Can I use SSR and SSG in the same project?
Pages are static by default, set prerender = false only for pages needing SSR.
This gives you best of both worlds - fast static pages and dynamic SSR where needed.
14 min read · Published on: Dec 2, 2025 · Modified on: Jan 22, 2026
Related Posts
Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation

Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation
Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload

Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload
Next.js Unit Testing Guide: Complete Jest + React Testing Library Setup


Comments
Sign in with GitHub to leave a comment