# 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: . **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 only** — **no 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`](https://github.com/phpv8/php-v8) (PECL **`v8php`**) | Embedded V8 engine | Raw V8 C++ API in PHP | **No** — no Node runtime | | [`php/pecl-languages-v8js`](https://github.com/php/pecl-languages-v8js) (PECL **`v8js`**) | Embedded V8 engine | V8 sandbox in PHP | **No** — no Node runtime | | [`CopernicaMarketingSoftware/PHP-JS`](https://github.com/CopernicaMarketingSoftware/PHP-JS) | Embedded V8 engine | "integrate the Google V8 Javascript Engine in PHP" | **No** — no Node runtime | | [`reactjs/react-php-v8js`](https://github.com/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`](https://github.com/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`](https://github.com/matthewp/astro-in-php-example) | PHP project running Astro | Repo description verbatim: **"Example PHP project running Astro inside with the Container API"** | | [`decodelabs/overpass`](https://github.com/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 (). 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/express`** — **Not 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-swa`** — **Not 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/partytown`** — **An 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/php`** — **Does 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 `curl`s 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 | ### 3.1 Static build (SSG) — the recommended path 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 statically** — `astro 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):** - On-demand rendering / server adapters — (four official adapters; "specific runtime"; default static pre-render; `output: 'static'`/`'server'`). - Configuration reference, `output` option — *(static-is-default confirmed via the ODR page above; the config-reference page itself was not live-fetched this session.)* - Adapter docs pages: Node , Netlify , Vercel , Cloudflare . *(Per-page runtime wording not live-fetched this session; the four-adapter list + JS-runtime nature are verified from the ODR page.)* - Partytown integration (an integration, not an adapter) — . **npm registry (primary source for "no PHP adapter"):** - `@astrojs/php` — → `{"error":"Not found"}` (HTTP 404). - `@astrojs/express` — *(former name of `@astrojs/node`; not in current list — deprecation field not live-fetched.)* - `@astrojs/azure` / `@astrojs/azure-swa` — , *(retired; not in current list.)* - `@astrojs/partytown` — *(integration, not a server adapter.)* **PHP↔JS / PHP↔Node projects (primary source for Q1):** - `phpv8/php-v8` (PECL **v8php**) — ("PHP 7.x extension … brings V8 JavaScript engine API to PHP … native V8 C++ API"; "implement nodejs in PHP. Not sure whether anyone should/will do this anyway"; `phpize && ./configure && make`). - `php/pecl-languages-v8js` (PECL **v8js**) — ("execute Javascript code in a secure sandbox from PHP"). - `CopernicaMarketingSoftware/PHP-JS` — ("A library to integrate the Google V8 Javascript Engine in PHP"). - `reactjs/react-php-v8js` — ("PHP library that renders React components on the server" — single-component V8 render, not an Astro build). - `matthewp/astro-in-php-example` — ("Example PHP project running Astro inside with the Container API" — real Node subprocess; requires a `node` binary). - `decodelabs/overpass` — ("Simple node.js bridge for PHP" — real Node subprocess; requires a `node` binary). *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.*