Skip to content

Server Mode (SSR)

shipyard fully supports Astro’s server-side rendering (SSR) mode, allowing you to render pages on-demand per request instead of at build time.

When to Use Server Mode

Server mode is ideal for:

  • Dynamic content that changes frequently
  • User authentication and personalized pages
  • Database-driven content that needs real-time updates
  • Applications requiring request-time data

For static content like documentation and blog posts, we recommend using static mode (the default) for better performance and simpler deployment.

Configuration

Step 1: Install an Adapter

Astro requires an adapter for server-side rendering. Choose one based on your deployment platform:

# For Cloudflare Workers/Pages
npm install @astrojs/cloudflare

# For Node.js
npm install @astrojs/node

# For Vercel
npm install @astrojs/vercel

# For Netlify
npm install @astrojs/netlify

Step 2: Configure Astro

Update your astro.config.mjs to enable server mode:

import cloudflare from '@astrojs/cloudflare' // or your chosen adapter
import tailwind from '@astrojs/tailwind'
import shipyard from '@levino/shipyard-base'
import shipyardDocs from '@levino/shipyard-docs'
import shipyardBlog from '@levino/shipyard-blog'
import { defineConfig } from 'astro/config'

export default defineConfig({
  output: 'server', // Enable server mode
  adapter: cloudflare(), // Your chosen adapter
  integrations: [
    tailwind({ applyBaseStyles: false }),
    shipyard({
      brand: 'My Site',
      title: 'My Site',
      tagline: 'Built with shipyard',
      navigation: {
        docs: { label: 'Docs', href: '/docs' },
        blog: { label: 'Blog', href: '/blog' },
      },
    }),
    shipyardDocs(),
    shipyardBlog(),
  ],
})

How It Works

When using server mode:

  1. Documentation and blog pages are rendered on-demand when requested
  2. Custom pages in src/pages/ can include dynamic server-side logic
  3. Static assets are still served from the build output

Example: Dynamic Custom Page

You can create dynamic pages that fetch data at request time:

---
// src/pages/dashboard.astro
import { Page } from '@levino/shipyard-base/layouts'

// This runs on every request
const userData = await fetchUserData()
---

<Page title="Dashboard" description="Your personal dashboard">
  <h1>Welcome, {userData.name}!</h1>
  <p>Last login: {userData.lastLogin}</p>
</Page>

Hybrid Rendering

For optimal performance, you can mix static and server-rendered pages using Astro’s hybrid mode:

export default defineConfig({
  output: 'hybrid', // Enable hybrid mode
  adapter: cloudflare(),
  // ...
})

In hybrid mode, pages are static by default. Add export const prerender = false to any page that needs server-side rendering:

---
// src/pages/api-data.astro
export const prerender = false

const data = await fetch('https://api.example.com/data')
---

Server Mode with Auth Middleware (Express)

A common pattern is to wrap Astro’s Node.js server behind an Express app that handles authentication (e.g. Passport.js, session stores). Here’s how to set that up with shipyard.

Configuration

When docs sit behind authentication middleware, pages must be rendered on every request so the middleware can check credentials. Set prerender: false explicitly:

import node from '@astrojs/node'
import shipyard from '@levino/shipyard-base'
import shipyardDocs from '@levino/shipyard-docs'
import tailwindcss from '@tailwindcss/vite'
import { defineConfig } from 'astro/config'
import appCss from './src/styles/app.css?url'

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'middleware' }),
  vite: {
    plugins: [tailwindcss()],
  },
  integrations: [
    shipyard({
      css: appCss,
      brand: 'Internal Docs',
      title: 'Internal Docs',
      navigation: {
        docs: { label: 'Docs', href: '/docs' },
      },
    }),
    shipyardDocs({ prerender: false }),
  ],
})

Express Wrapper

Use Astro’s Node.js adapter in middleware mode and mount it inside Express:

// server.mjs
import express from 'express'
import { handler as astroHandler } from './dist/server/entry.mjs'

const app = express()

// Your auth middleware (e.g. Passport, session)
app.use(session({ /* ... */ }))
app.use(passport.initialize())
app.use(passport.session())
app.use((req, res, next) => {
  if (req.isAuthenticated()) return next()
  res.redirect('/login')
})

// Mount Astro as the final handler
app.use(astroHandler)

app.listen(3000)

Root Path Redirect

When your docs are the only content, redirect the root path to /docs:

---
// src/pages/index.astro
return Astro.redirect('/docs', 302)
---

This avoids needing a routeBasePath workaround and keeps the URL structure clean.

Key Points

SettingWhy
output: 'server'Enables SSR so middleware runs on every request
adapter: node({ mode: 'middleware' })Lets you mount Astro inside Express
shipyardDocs({ prerender: false })Prevents docs from being pre-rendered at build time, which would bypass auth

Live Demo

See server mode in action: Server Mode Demo

Deployment Notes

Each adapter has specific deployment requirements. Consult the Astro documentation for your chosen platform: