Frameworks & Hosting8 min read

How to Integrate Railway with Your Headless CMS

Connect Railway to your headless CMS so content publishes can trigger redeploys, update hosted apps, and keep production pages in sync without manual rebuilds.

Published April 29, 2026
01 โ€” Overview

What is Railway?

Railway is a cloud platform for deploying web apps, APIs, workers, cron jobs, and databases from GitHub repositories, Dockerfiles, or templates. Teams use it to run framework apps such as Next.js, Remix, Astro, Express, and Django with built-in environments, variables, logs, metrics, and deploy triggers.


02 โ€” The case for integration

Why integrate Railway with a headless CMS?

If your site runs on Railway but your content changes somewhere else, you need a reliable way to connect publishing events to your deployed app. Otherwise, editors publish an article, a developer clicks redeploy, someone clears a cache, and the live page still might show yesterday's headline for 20 minutes.

For Frameworks & Hosting setups, the most common pattern is simple: your frontend or API runs on Railway, while your content lives in a structured back end. When content is published, updated, or deleted, a webhook or server-side function tells Railway what changed and triggers the right action, such as redeploying a static build, updating an environment variable, calling a revalidation endpoint, or notifying a worker.

A headless CMS can work for this, but structured content makes the integration much easier to trust. With Sanity, content in the Content Lake is typed JSON, GROQ selects only the fields your Railway service needs, and webhooks fire on specific publish events. The alternative is usually disconnected glue code: scheduled rebuilds every 10 minutes, manual deploy buttons, broad cache purges, or API handlers that fetch more content than they need.


03 โ€” Architecture

Architecture overview

A typical Railway integration starts when an editor publishes content in Sanity Studio. Sanity writes the structured JSON document to the Content Lake, then a webhook or Function runs on the publish event. That event includes the document ID, type, and operation, but you usually fetch the latest document before acting so you don't depend on a partial payload. The webhook handler uses @sanity/client and GROQ to fetch exactly what Railway needs, for example a post ID, slug, title, updated timestamp, author reference, and release state. From there, you have two common paths. For a static site hosted on Railway, the handler can call Railway's Deploy Trigger URL with a POST request. For more control, it can call Railway's GraphQL API at https://backboard.railway.app/graphql/v2, update a service variable such as SANITY_CONTENT_REV, and redeploy a specific service instance. After Railway receives the request, it starts a deployment or updates the running service depending on your setup. The end user then sees the new content through the Railway-hosted app. That app may read directly from the Content Lake at request time, use cached API responses with tag-based invalidation, or serve a rebuilt static page. The trade-off is build time. A full redeploy is easy to reason about, but larger sites may prefer route-level revalidation or a worker that updates only affected pages.


04 โ€” Use cases

Common use cases

๐Ÿš†

Publish-triggered app deploys

Trigger a Railway deploy when a Sanity document moves from draft to published so static pages rebuild without a developer pressing a button.

โš™๏ธ

Content-driven API services

Run an Express, Fastify, or Hono API on Railway that serves structured Sanity content to apps, kiosks, or internal tools.

๐Ÿงช

Preview environments for content releases

Connect Sanity Content Releases to Railway environments so editors can review staged launches against the same app code production uses.

๐Ÿ“ฌ

Background content workers

Use a Railway worker to process Sanity publish events, generate feeds, warm caches, or notify downstream services.


05 โ€” Implementation

Step-by-step integration

  1. 1

    Create your Railway project

    Sign in to Railway, create a new project, and deploy from a GitHub repository, Dockerfile, or template. Add the framework service you want to run, such as Next.js, Remix, Astro, Express, or Django. In the service settings, create a Deploy Trigger if you want Sanity publishes to start a new deployment.

  2. 2

    Add Railway credentials

    For a simple redeploy flow, copy the Railway Deploy Trigger URL. For GraphQL API control, create a Railway API token and note your project ID, environment ID, and service ID. Add these values as secrets in your webhook service, Sanity Function, or Railway-hosted middleware.

  3. 3

    Model the content in Sanity Studio

    Define the fields your Railway app actually needs. For a marketing page, that might be title, slug, seoTitle, heroImage, body, publishDate, and references to author or product documents. Keep the schema specific so GROQ can return a small payload instead of a whole document tree.

  4. 4

    Create the sync trigger

    In Sanity, create a webhook that fires on create, update, and delete events for the document types your Railway app renders. Use a GROQ-powered filter such as _type in ["post", "page", "product"] and only send events for published documents if drafts shouldn't trigger deploys.

  5. 5

    Connect Sanity to Railway

    Build a small webhook handler or Sanity Function. It should receive the Sanity event, fetch the latest content with @sanity/client and GROQ, then call either the Railway Deploy Trigger URL or the Railway GraphQL API to redeploy or update the target service.

  6. 6

    Test the full path

    Publish a test document in Sanity Studio, confirm the webhook fires, check Railway logs for the incoming request, and verify that the deployed app shows the updated slug or title. Test deletes too. They often need different cache or redirect behavior than updates.


06 โ€” Code

Code example

typescriptapp/api/sanity-to-railway/route.ts
import {createClient} from '@sanity/client'
import {GraphQLClient, gql} from 'graphql-request'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-02-19',
  token: process.env.SANITY_READ_TOKEN,
  useCdn: false,
})

const railway = new GraphQLClient('https://backboard.railway.app/graphql/v2', {
  headers: {Authorization: `Bearer ${process.env.RAILWAY_TOKEN}`},
})

const mutation = gql`
  mutation Redeploy($input: VariableUpsertInput!, $environmentId: String!, $serviceId: String!) {
    variableUpsert(input: $input)
    serviceInstanceRedeploy(environmentId: $environmentId, serviceId: $serviceId) { id }
  }
`

export async function POST(req: Request) {
  const event = await req.json()
  const doc = await sanity.fetch(
    `*[_id == $id][0]{_id, _rev, _type, title, "slug": slug.current}`,
    {id: event._id}
  )

  if (!doc) return Response.json({ok: true, skipped: true})

  await railway.request(mutation, {
    environmentId: process.env.RAILWAY_ENVIRONMENT_ID!,
    serviceId: process.env.RAILWAY_SERVICE_ID!,
    input: {
      projectId: process.env.RAILWAY_PROJECT_ID!,
      environmentId: process.env.RAILWAY_ENVIRONMENT_ID!,
      serviceId: process.env.RAILWAY_SERVICE_ID!,
      name: 'SANITY_CONTENT_REV',
      value: `${doc._id}:${doc._rev}`,
    },
  })

  return Response.json({ok: true, synced: doc.slug})
}

07 โ€” Why Sanity

How Sanity + Railway works

Build your Railway integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to connect Railway-hosted apps to production content.

Start building free โ†’

08 โ€” Comparison

CMS approaches to Railway

CapabilityTraditional CMSSanity
Publish-triggered Railway deploysOften needs a plugin, a manual deploy button, or a scheduled rebuild outside the editorial flow.Webhooks can fire on specific mutations, and Functions can run server-side sync logic without a separate worker.
Field-level data for hosted appsPage content is often stored as rendered HTML, so apps may need parsing before they can use it.GROQ selects exact fields and joins referenced documents in one query for Railway services.
Environment-aware workflowsProduction and preview content often depend on separate sites or manual staging steps.Datasets, perspectives, and Content Releases can map to Railway environments for preview and production checks.
Server-side sync logicCustom sync usually runs inside the same platform or on a separate server you maintain.Functions can fetch, transform, validate, and forward content events before they reach Railway.
Multi-channel deliveryContent is usually tied closely to web pages, which makes reuse in APIs or AI agents harder.The Content Lake can serve Railway apps, mobile apps, and production AI agents through Agent Context.

09 โ€” Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Railway and 200+ other tools.