Serverless
Run the public eddev frontend on Vercel while WordPress owns content and routing.
eddev serverless mode runs the public React frontend on a JavaScript host such as Vercel, while WordPress remains the CMS and routing authority.
In production, the usual shape is:
cms.website.compoints at the WordPress origin, usually behind Cloudflare.website.compoints at the Vercel deployment.- Vercel receives public page requests, asks WordPress for the matched route data, then server-renders the React view.
- WordPress still resolves the route, executes the view/block GraphQL files, owns
/wp-admin, and provides runtime query and mutation endpoints.
The Vercel app is not a separate router. It is a rendering and API layer in front of the WordPress origin.
Deploying To Vercel
For a normal project:
- Import the site's GitHub repository into Vercel.
- Add
SITE_URLin Vercel project environment variables. - Set
SITE_URLto the WordPress origin, including the protocol, for examplehttps://cms.staging.sff.org.au/. - If the WordPress origin uses eddev Access Control, add
SITE_API_KEYin Vercel as well. - Deploy.
During the build, eddev reads SITE_URL and uses it as the origin for WordPress route data, global app data, named GraphQL query hooks, mutations, plugin assets, uploads, and proxied WordPress/admin requests.
Do not point SITE_URL at the Vercel frontend. It should point at the WordPress origin, usually the cms. host.
Request Flow
When a visitor requests a page from the Vercel deployment:
- The serverless app normalizes the URL and asks WordPress for route data with
?_props=1and_ssr=1. - WordPress resolves the request through the normal template hierarchy, runs the paired view GraphQL file, and returns the JSON route payload.
- The serverless app replaces origin URLs where configured, fetches app data, and renders the matching React view.
- Browser navigation continues through eddev's route data endpoints instead of loading a whole WordPress HTML page.
Runtime GraphQL hooks follow the same split:
- browser-side query hooks on Vercel call
/_data/query/{queryName} - browser-side mutations on Vercel call
/_data/mutation/{mutationName} - the serverless app forwards those to the WordPress REST endpoints under
/wp-json/ed/v1/query/and/wp-json/ed/v1/mutation/
Server routes from server/routes/ are deployed with the serverless app. See RPC Functions for route authoring and React usage.
Basic Configuration
Serverless behaviour is configured in ed.config.json. The serverless key controls deployment and routing behaviour, while the cache key controls how both the WordPress and JavaScript server sides cache data.
{
"$schema": ".ed.config.schema.json",
"version": "2",
"serverless": {
"enabled": true,
"uploads": "remote",
"plugins": "remote",
"admin": "hide",
"themeAssets": ["assets/**/*"],
"originProtection": {
"requireLogin": false
},
"endpoints": {
"cms.website.com": "website.com"
}
},
"cache": {
"*": {
"serverless": {
"isr": true,
"dataCache": "in-memory"
},
"wordpress": {
"cacheHeaders": true,
"transients": true
},
"pageDataTTL": 300,
"appDataTTL": 300,
"queryHooksTTL": 300
}
}
}serverless
| Option | What It Does |
|---|---|
enabled | Enables serverless deployment support. This is normally true. |
uploads | Controls whether /wp-content/uploads/** URLs are left as remote origin URLs or proxied through the serverless deployment. Use remote by default. |
plugins | Controls whether /wp-content/plugins/** URLs are left as remote origin URLs or proxied through the serverless deployment. Use remote by default. |
admin | proxy forwards WordPress admin/API URLs through the deployment. hide returns a 404 for /wp-admin, /wp-json, /wp-login.php, GraphQL, and PHP-style URLs in production. |
originProtection.requireLogin | Force-enables origin protection from config. This prevents non-logged-in visitors from accessing the WordPress origin frontend, while still allowing the serverless frontend to fetch with an API key. |
themeAssets | Static theme asset folders to include in the serverless deployment. The starter theme uses ["assets/**/*"]. |
endpoints | Maps WordPress hostnames to serverless hostnames. This tells the WordPress-hosted SPA/admin where to call serverless endpoints such as RPC functions. |
cors.origins | Additional allowed CORS origins. WordPress and serverless hosts from endpoints are already allowed. |
csp | Content Security Policy settings for the serverless app, including autodetected origins, common origins, nonces, and directive values. |
serverless.endpoints values are hostnames, not full URLs:
{
"serverless": {
"endpoints": {
"cms.staging.sff.org.au": "sff-staging.vercel.app",
"cms.sff.org.au": "www.sff.org.au"
}
}
}You can use "*" as a fallback key, but explicit hostnames are better for production and staging because they make the CMS-to-frontend relationship clear.
The WordPress PHP layer injects window.SERVERLESS_ENDPOINT into the WordPress frontend and admin when it can match the current WordPress host in serverless.endpoints. The RPC client uses that value when the site is running in WordPress-hosted SPA mode, so incorrect endpoint mappings usually show up as RPC calls going to the wrong host.
cache
The cache object is a map of WordPress origin hostnames to cache settings. Use "*" for the default, and add host-specific entries when staging and production need different behaviour.
Cache host keys are matched as hostnames. Ports are ignored, exact hostnames win over wildcard patterns, wildcard patterns like "*.website.com" are supported, and "*" is used as the fallback.
| Option | What It Does |
|---|---|
pageDataTTL | Seconds to cache route data and rendered page responses. |
appDataTTL | Seconds to cache global app data from views/_app.graphql. |
queryHooksTTL | Seconds to cache named runtime GraphQL query hook responses. |
serverless.isr | Enables serverless response caching/ISR for rendered pages, route data, and named query data. Set it to false for dynamic, no-store serverless responses. |
serverless.dataCache | Controls the serverless origin-data cache. Use in-memory for the normal LRU/SWR cache, or none to fetch WordPress every time the serverless function needs data. |
wordpress.cacheHeaders | Allows WordPress data responses to emit cache headers. |
wordpress.transients | Allows WordPress to cache data in transients. |
serverless.isr and serverless.dataCache control different cache layers:
| Configuration | Behaviour |
|---|---|
isr: true, dataCache: "in-memory" | Normal production mode. Serverless responses can be cached, and serverless revalidation/data requests are protected by an in-memory data cache. |
isr: true, dataCache: "none" | Serverless responses can still be cached, but every revalidation fetch goes back to WordPress. |
isr: false, dataCache: "in-memory" | Serverless responses are dynamic/no-store, but repeated WordPress data fetches can still use the serverless in-memory cache. |
isr: false, dataCache: "none" | Fully dynamic serverless mode. Responses are no-store and serverless data fetches go back to WordPress every time. |
Queries and mutations are handled differently:
- route data and app data use the page/app TTLs
- named query hooks use
queryHooksTTL - mutation responses are not cached
- requests with varied auth/session headers avoid the shared named-query data cache
Individual GraphQL query files can still set # ttl: 300 or # nocache; see GraphQL caching.
Origin Access Control
Use eddev's Access Control feature to protect the WordPress origin. Do not use WP Engine's password protection for this, because Vercel still needs to fetch WordPress route data, GraphQL results, uploads, and REST endpoints without receiving a human password prompt.
In WordPress:
- Open Settings → Access Control.
- Generate an API key.
- Enable API key protection if it is not force-enabled by config.
- Optionally enable simple password protection for people who should be able to preview the CMS-hosted frontend directly.
- Add the generated key to Vercel as
SITE_API_KEY.
When SITE_API_KEY is set, eddev adds it to server-to-server WordPress requests as the x-ed-api-key header. The PHP origin checks that header before allowing access.
Access Control allows:
- logged-in WordPress users
- requests with a valid eddev API key
- users with the configured basic-auth credentials, when password protection is enabled
Everyone else receives an Access Denied page or a password prompt, depending on the settings.
An API key only allows a request to reach the origin. It does not grant WordPress permissions or log the request in as a user.
For stricter projects, set serverless.originProtection.requireLogin to true. That force-enables API-key protection from committed config, so the WordPress origin cannot accidentally be left public through an admin setting.
Domain Setup
The common production setup is:
| Host | Points To | Purpose |
|---|---|---|
cms.website.com | WP Engine or another WordPress host, often proxied through Cloudflare | WordPress admin, CMS preview, GraphQL, REST, uploads, and route data origin |
website.com / www.website.com | Vercel | Public server-rendered frontend and serverless routes |
Keep the WordPress origin reachable by the Vercel deployment. If Cloudflare sits in front of cms.website.com, make sure it passes the x-ed-api-key header and does not add its own interactive challenge to server-to-server requests.
Deployment Checklist
SITE_URLin Vercel points to the WordPress origin, for examplehttps://cms.website.com/.SITE_API_KEYis set in Vercel when Access Control is enabled.- The same API key exists in WordPress under Settings → Access Control.
serverless.endpointsmaps each WordPress hostname to the correct Vercel/public hostname.serverless.adminis set intentionally, usuallyhidefor public deployments.cache["*"]or a host-specific cache entry sets realistic TTLs for route data, app data, and runtime query hooks.- Cloudflare or host-level protection does not block Vercel's server-to-server origin requests.