Docs / CMS & Website Platforms / Deploy an Astro Site with CMS Backend

Deploy an Astro Site with CMS Backend

By Admin · Mar 15, 2026 · Updated Apr 23, 2026 · 188 views · 3 min read

Astro is a modern web framework that delivers lightning-fast websites by shipping zero JavaScript by default. It supports multiple UI frameworks (React, Vue, Svelte) and integrates seamlessly with headless CMS backends. This guide covers deploying an Astro site with CMS integration on your VPS.

Why Astro?

  • Zero JS by default: Pages ship as pure HTML unless you opt into client-side JavaScript
  • Islands architecture: Interactive components hydrate independently
  • Content collections: Type-safe Markdown/MDX content with schema validation
  • Framework agnostic: Use React, Vue, Svelte, or Solid components together
  • SSR support: Server-side rendering with Node.js adapter for dynamic content

Create an Astro Project

# Create new project
npm create astro@latest my-astro-site
cd my-astro-site

# Add integrations
npx astro add node    # For SSR deployment
npx astro add react   # If using React components
npx astro add tailwind # For Tailwind CSS
npx astro add mdx     # For MDX support

Content Collections (Local CMS)

// src/content/config.ts
import { defineCollection, z } from 'astro:content'

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    publishedAt: z.date(),
    updatedAt: z.date().optional(),
    author: z.string(),
    tags: z.array(z.string()),
    image: z.string().optional(),
    draft: z.boolean().default(false),
  }),
})

const docs = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    order: z.number(),
    section: z.string(),
  }),
})

export const collections = { blog, docs }

Fetching from a Headless CMS

// src/lib/cms.ts
// Example: Fetching from Directus, Strapi, or any REST API

export async function getPosts() {
  const res = await fetch(`${import.meta.env.CMS_URL}/items/posts?filter[status][_eq]=published&sort=-published_at`, {
    headers: {
      'Authorization': `Bearer ${import.meta.env.CMS_TOKEN}`,
    },
  })
  const { data } = await res.json()
  return data
}

// src/pages/blog/[slug].astro
---
import { getPosts } from '../../lib/cms'
import Layout from '../../layouts/Layout.astro'

export async function getStaticPaths() {
  const posts = await getPosts()
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }))
}

const { post } = Astro.props
---

<Layout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    <time>{new Date(post.published_at).toLocaleDateString()}</time>
    <div set:html={post.content} />
  </article>
</Layout>

SSR Deployment on VPS

// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'

export default defineConfig({
  output: 'hybrid',  // Static by default, SSR opt-in per page
  adapter: node({
    mode: 'standalone',
  }),
  server: { port: 4321, host: '127.0.0.1' },
})

# Build
npm run build

# The output is in dist/
# Entry point: dist/server/entry.mjs

# systemd service
sudo cat > /etc/systemd/system/astro-site.service > /var/log/astro-rebuilds.log

Best Practices

  • Use hybrid rendering: Static pages for content, SSR for dynamic pages like search
  • Leverage content collections for local Markdown content with type safety
  • Ship minimal JavaScript: Use client:load, client:idle, or client:visible directives wisely
  • Set up image optimization: Use Astro's built-in <Image> component for responsive images
  • Cache aggressively: Astro's hashed asset filenames make long cache headers safe
  • Configure webhooks from your CMS to trigger rebuilds on content changes

Was this article helpful?