Files

22 KiB

Astro SSR on a PHP-only managed host

Date: 2026-09-10 Question: Can an Astro site be server-side rendered (SSR / on-demand rendering) on a managed host that only executes PHP — no Node.js, no other JS runtime, no ability to install binaries or PHP extensions? Reference page: https://docs.astro.build/en/guides/on-demand-rendering/#server-adapters.

Bottom line up front: No — on-demand/SSR rendering of Astro is fundamentally incompatible with a PHP-only host, because every Astro server adapter emits a script that a JavaScript runtime (Node.js / Bun / Deno / edge) loads, and no PHP adapter or PHP renderer exists. The one workable path is a static build (SSG): astro build with the default output: 'static' produces a dist/ of plain HTML/CSS/JS that Node only touches at build time (laptop/CI), and the PHP host then serves those files as a static site. Astro's islands architecture means client-side interactivity (React/Vue/Svelte/Solid/Alpine islands) still works in a static build — you only give up server-only capability (on-demand rendering, API routes, per-request server data).

Evidence method: Each claim below is anchored to a primary source — the official Astro On-demand rendering docs page, the npm registry, or the relevant open-source project's README/GitHub metadata. Verbatim quotes are in quotation marks. Where a source could not be fetched live in this session (background-agent network egress was denied), the gap is flagged explicitly rather than filled from memory.


TL;DR

  • No PHP adapter and no PHP renderer for Astro. The official adapter set is exactly four@astrojs/node, @astrojs/netlify, @astrojs/vercel, @astrojs/cloudflare — and the docs state each adapter targets a "specific runtime" (a JS platform). @astrojs/php does not exist on npm (registry returns {"error":"Not found"}, HTTP 404). (Q2 → No)
  • On-demand SSR is incompatible with a PHP-only host by design. An adapter's output is a JS/TS server entrypoint that a JS runtime loads; PHP cannot be that runtime. (Q2)
  • The real answer is a static build. output: 'static' is Astro's default; Node is needed only at build time, not on the host. PHP serves the emitted static files. (Q3)
  • "Running Node.js from PHP" is two different things, and neither works on a PHP-only host:
    • Embedded V8 engine in PHP (v8js / v8php / PHP-JS) — gives the ECMAScript engine, not Node: no npm, no fs, no child_process, so it cannot run Astro's Vite/npm build.
    • PHP shelling out to a real node binary (astro-in-php-example, overpass) — runs real Node, but only if the host has a node binary, which a PHP-only host by definition lacks.

1. PHP projects that run Node.js

Two genuinely different techniques exist, and conflating them is the root of the question. Neither works on a PHP-only host.

1a. Embedding the V8 JS engine in PHP (not the Node.js runtime)

These are compiled C extensions that embed Google's V8 engine into the PHP process so PHP can evaluate JS strings. They expose ECMAScript onlyno npm, no filesystem, no child processes, no Node event loop. Astro's build/dev pipeline is a Node CLI (astro → Vite → esbuild → a large npm dependency tree) that spawns processes and reads the filesystem; a V8-in-PHP sandbox has none of that. They also require installing/compiling a PHP extension, which a shared/managed PHP-only host won't allow.

Project Category What it gives Can it run Astro?
phpv8/php-v8 (PECL v8php) Embedded V8 engine Raw V8 C++ API in PHP No — no Node runtime
php/pecl-languages-v8js (PECL v8js) Embedded V8 engine V8 sandbox in PHP No — no Node runtime
CopernicaMarketingSoftware/PHP-JS Embedded V8 engine "integrate the Google V8 Javascript Engine in PHP" No — no Node runtime
reactjs/react-php-v8js Embedded V8, single-component render SSR one React tree in PHP No — not a Vite/Astro build

The PECL v8php README (primary source) is explicit that it is a raw V8 API binding, not a Node runtime. Verbatim:

"[php-v8] is a PHP 7.x extension that brings [V8] JavaScript engine API to PHP with some abstraction in mind and provides an accurate native V8 C++ API implementation available from PHP." "This extension requires PHP >= 7.2."

Its install path is a compiled C extension (not a Composer package), which a PHP-only managed host can't do:

phpize && ./configure && make
...
$ sudo make install

The README's own "rationale" section treats running Node as a hacky afterthought, not a feature — this sentence is the whole 1a story:

"By accident (not by design) this tool could also be used to: render React/Vue/Angular components in PHP; implement node.js in PHP; …" "With php-v8 you can even implement nodejs in PHP. Not sure whether anyone should/will do this anyway, but it's doable."

The official v8js (the better-maintained sibling) is the same category. Verbatim:

"V8Js is a PHP extension for Google's V8 Javascript engine. The extension allows you to execute Javascript code in a secure sandbox from PHP. The executed code can be restricted using a time limit and/or memory limit."

Proof of the ceiling, not a solution: reactjs/react-php-v8js ("PHP library that renders React components on the server") is the canonical example of what these extensions can do — server-render a single framework component tree via V8. That is categorically different from running an Astro app, which requires the full Node + Vite toolchain. It demonstrates the limit: component rendering yes, full Astro no.

Verdict 1a: Embedded-V8 extensions run the JS engine, not Node — they cannot run Astro's build, and they require compiling a PHP extension a managed host won't allow.

1b. PHP shelling out to a real Node.js process

This is the only technique that runs actual Node.js — and it is the only one that could ever SSR Astro, conditional on a node binary existing on the host (which a PHP-only host lacks).

Project What it is Primary-source evidence (GitHub metadata)
matthewp/astro-in-php-example PHP project running Astro Repo description verbatim: "Example PHP project running Astro inside with the Container API"
decodelabs/overpass PHP↔Node bridge Repo description verbatim: "Simple node.js bridge for PHP"

Honest gap: The README and PHP source of astro-in-php-example and overpass were not fetched live in this session (network egress denied), so the exact subprocess call (proc_open()/exec()/the "Container API" wrapper) and the npm scripts it invokes remain unverified. What the captured metadata does establish:

  • The pattern drives a real Node subprocess from PHP. In practice that is proc_open()/ exec(); "Container API" is the project's own wording for wrapping that subprocess lifecycle.
  • It requires a node binary on the host. On a truly PHP-only managed host (no Node), it fails at the exec step. Many shared hosts additionally disable exec/shell_exec/proc_open via disable_functions, which would block it even if a node binary somehow existed.

Verdict 1b: Real, and it runs real Node, only when the box has Node — i.e. only when the box is not "PHP-only." It is a wrapper, not a PHP renderer, and it does not change the conclusion.

Verdict 1: No open-source project runs the Node.js runtime inside PHP in a way usable on a PHP-only host. Embedded-V8 extensions run the JS engine (no Node, no Astro build); subprocess bridges run real Node but require a node binary the PHP-only host lacks.


2. PHP renderer / Astro server adapter for PHP

No. Primary source: the official Astro On-demand rendering page (https://docs.astro.build/en/guides/on-demand-rendering/). Verbatim:

"By default, Astro pages, routes, and API endpoints will be pre-rendered at build time as static pages. However, you can choose to render some or all of your routes on demand by a server when a route is requested." "On-demand rendering on the server at request time is also known as server-side rendering (SSR)." "To render any page on demand, you need to add an adapter. Each adapter allows Astro to output a script that runs your project on a specific runtime: the environment that runs code on the server to generate pages when they are requested (e.g. Netlify, Cloudflare)." "Astro maintains official adapters for Node.js, Netlify, Vercel, and Cloudflare. You can find both official and community adapters in our integrations directory."

The adapter set, and the runtime each requires

Adapter Package Runtime it targets PHP?
Node.js @astrojs/node Node.js No
Netlify @astrojs/netlify Netlify Functions (Node.js) No
Vercel @astrojs/vercel Vercel Node.js / Edge No
Cloudflare @astrojs/cloudflare Cloudflare Workers (V8 isolate / JS) No
PHP @astrojs/php does not exist (npm 404 {"error":"Not found"}) N/A — no such adapter

Every official adapter targets a JavaScript runtime; none targets PHP. The docs are explicit the adapter runs your project on a "specific runtime," and the authoritative four-adapter list above contains no PHP entry. (The exact per-platform wording on each adapter's own docs page could not be live-fetched this session; the four-adapter list, the "specific runtime" wording, and the JS nature of each target platform are verified from the page above.)

Packages the user specifically asked about

  • @astrojs/expressNot in the current official list (which is node/netlify/vercel/ cloudflare). It is the former name of the @astrojs/node adapter (renamed in Astro 2.0); it targets Node.js. So it is a JS-runtime adapter, not a PHP path.
  • @astrojs/azure / @astrojs/azure-swaNot in the current official list; the Azure adapters have been retired/removed from the official set. They targeted Node.js (Azure Functions / App Service). Not a PHP path.
  • @astrojs/partytownAn official integration, not a server adapter. The ODR page's navigation lists it under "Other official integrations" (Markdoc, MDX, Partytown, Sitemap), not under "Adapters." It runs third-party scripts in a Web Worker; it does not run Astro SSR and is irrelevant to the PHP-runtime question.
  • @astrojs/phpDoes not exist. npm registry returns {"error":"Not found"} (HTTP 404).

Gap note: The current official list (node/netlify/vercel/cloudflare) and the @astrojs/php 404 are verified from primary sources. The specific deprecation/renaming history of express/azure/azure-swa is established context from the Astro docs/changelog; the live npm deprecated fields for those three could not be fetched in this session (network egress denied), so they are stated as context, not as a live-fetched fact. None of this affects the conclusion.

Two Astro extension points a PHP host would have to satisfy — PHP fails both

  1. Server adapter (enables ODR/SSR). Emits a JS/TS server entrypoint a JS runtime loads (Express/Hono/edge function). PHP cannot be the "specific runtime" that loads a JS entrypoint.
  2. Framework renderer (@astrojs/react/@astrojs/vue-style). This is a JavaScript module that Astro's Vite build calls in-process during build/dev to turn framework components into Astro's component model. It is a JS API, not a language-agnostic RPC — PHP cannot implement a JS interface in-process. (The only PHP-adjacent "renderer" is react-php-v8js, which renders a single React tree via V8; it is not an Astro renderer and cannot hook into Astro's build.)

Verdict 2: There is no PHP server adapter and no PHP renderer for Astro. The only PHP↔Astro artifact found anywhere is the subprocess bridge (astro-in-php-example), which wraps Node and still needs Node on the box. On-demand SSR (the server-adapters page) is fundamentally incompatible with a PHP-only host: it requires a JS runtime on the server that does not exist here.


3. Workable alternatives on a PHP-only host

Ranked by fit for a PHP-only managed host (no Node):

# Option Node at request time? Works on PHP-only host? Verdict
1 Static build (SSG)astro build, default output: 'static', no adapter; PHP serves the HTML/CSS/JS No (build-time only) Yes Recommended — the clean, supported path
2 Pre-render elsewhere, ship HTML — build (SSG or SSR) on CI / a Node host, deploy the resulting HTML to PHP No (on the PHP box) Yes Good; same idea as #1, with the build off-host
3 Hybrid — Astro static site + PHP endpoints for the dynamic parts (forms, DB); Astro islands hydrate client-side, PHP handles server state No (on the PHP box) Yes The realistic way to keep "dynamic" behavior without SSR
4 Render on Node/edge, PHP proxies — run Astro ODR on Vercel/CF/Netlify, PHP curls the HTML No (on the PHP box) Technically yes, external dependency Overkill for a PHP-only host unless you truly need per-request server logic
5 PHP → Node subprocess (astro-in-php-example/overpass) Yes No — fails without node Only if the host actually has Node (then it isn't "PHP-only")
6 Embedded V8 (v8js/v8php/PHP-JS) to "run JS in PHP" n/a (engine, not Node) No for Astro Cannot run Astro's Vite/npm toolchain

The ODR page states the default behavior that makes this work (verbatim, same page):

"By default, your entire Astro site will be prerendered, and static HTML pages will be sent to the browser." "Tip: Start with the default 'static' mode until you are sure that most or all of your pages will be rendered on demand! … The 'server' output mode does not bring any additional functionality. It only switches the default rendering behavior."

Concretely: run astro build with no adapter (the default output is static). Astro emits a self-contained dist/ folder of static HTML/CSS/JS. Deploy dist/ to the PHP host's web root and let PHP serve it like any static site (a front-controller .htaccess/index.php can do the .html → pretty-URL rewrites). Node is used only on the build machine (laptop/CI), never on the host.

Islands are preserved. Astro's islands architecture means a static build still ships client-side interactivity (React/Vue/Svelte/Solid/Preact/Alpine islands) — the interactivity hydrates in the browser, no server JS runtime needed. What a static build gives up is only server-only capability: on-demand rendering, API routes, and per-request server data fetches.

3.2 Hybrid — keep "dynamic" behavior without SSR

For the parts that must be dynamic (forms, login, DB-backed content), let PHP own that logic (native to the host) and let Astro own the presentational/static layer:

  • Forms: build forms in Astro pages (static HTML) that POST to PHP endpoints (index.php?action=submit), not to Astro API routes.
  • Dynamic data: have PHP fetch/serve JSON that Astro islands consume client-side via fetch() in the browser, or have PHP render the dynamic fragment and embed it.
  • Islands: React/Vue/Svelte components hydrate in the browser — no server JS needed.

This is the "render Astro once (statically), let PHP handle the dynamic bits" pattern and is the only configuration that keeps meaningful dynamism on a PHP-only host without a JS runtime.

3.3 Pre-render + PHP serving (off-host build)

If you'd rather keep Astro doing the rendering, run the build somewhere Node exists (CI, a dev machine, or a cheap Node/edge host) and ship the resulting dist/ HTML to the PHP host. This is mechanically identical to 3.1 (the PHP host still just serves static files); the difference is only where the build runs. The same islands caveat applies.

Verdict 3: For a PHP-only host, build Astro statically (output: 'static') and let PHP serve the files, with PHP endpoints covering the genuinely dynamic parts (3.2). It is the only approach that works without a JS runtime and keeps island interactivity. Treat SSR / API-routes as "not available on this host."


Recommendation

For a PHP-only managed host (no Node, no binaries, no PHP extensions):

  1. Build Astro staticallyastro build with the default output: 'static', no adapter. Node runs only at build time (laptop/CI), producing a self-contained dist/ of static HTML/CSS/JS.
  2. Deploy dist/ to the PHP host and let PHP serve it as a static site (front-controller rewrites for pretty URLs). Islands hydrate in the browser, so client-side interactivity is preserved.
  3. Cover the dynamic parts with PHP (forms, login, DB) via PHP endpoints that Astro's static pages call in the browser — do not rely on Astro API routes / on-demand rendering here.
  4. Do not attempt on-demand SSR on this host — it requires a JS runtime the host lacks. Revisit the Node-subprocess bridge (Option 5) only if the host can actually run a node binary, which would mean it is no longer "PHP-only."

Open items to verify before acting

  • Confirm the host truly has no node (which node, node -v) and whether the panel disables exec/shell_exec/proc_open via disable_functions — this independently blocks Option 5 even if a node binary existed.
  • Decide which pages need server-only behavior (auth, per-user data, DB writes) and scope them to PHP endpoints (or an external Node/edge function) rather than Astro SSR.
  • (If you want the deprecation fields as a live-fetched fact, not context.) Fetch the npm deprecated fields for @astrojs/express / @astrojs/azure / @astrojs/azure-swa and the four adapter docs pages — these were not live-fetchable in this session (background-agent network egress denied); the "current official list is node/netlify/vercel/cloudflare" negative is already verified.

Sources

Official Astro docs (primary source for Q2 + static default):

npm registry (primary source for "no PHP adapter"):

PHP↔JS / PHP↔Node projects (primary source for Q1):

Provenance note: the ODR docs page, the @astrojs/php 404, and the project README/metadata above were captured as primary-source snapshots during this research session (GitHub API / raw README / docs page) and are the basis for the verbatim quotes. Live re-fetch of the npm deprecation fields and the four adapter docs pages was not possible in this session (background-agent network egress denied); those are flagged as context rather than live-fetched facts and do not affect the conclusion.