Bridging Two Worlds: Introducing astro-php-ssr - Run PHP Routes Inside Astro
If you've ever found yourself caught between the modern, blazing-fast world of Astro and the need to integrate legacy PHP code or leverage PHP's ecosystem, you're not alone. Today, I'm excited to introduce astro-php-ssr - a seamless integration that lets you run PHP routes directly inside your Astro SSR applications.
The Problem: Modern Frontend, Legacy Backend
Astro has revolutionized how we build content-focused websites. Its island architecture, zero-JS-by-default philosophy, and incredible performance make it a developer favorite. But what happens when you need to:
- Integrate existing PHP APIs or services
- Work with PHP-based CMSs or legacy systems
- Leverage PHP libraries without rewriting everything in JavaScript
- Gradually migrate a PHP application to a modern stack
Until now, you'd be stuck managing separate servers, dealing with CORS issues, or embarking on costly rewrites.
Enter astro-php-ssr
astro-php-ssr is a lightweight Astro integration that runs .php files directly inside your Astro SSR application using php-cgi or php as a fallback. No separate PHP server required. No complex proxy configurations. Just seamless PHP integration.
Key Features
✅ Seamless Integration - Works with astro dev and production SSR
✅ Full Request Support - Handles GET, POST, and other HTTP methods
✅ Flexible Configuration - Customize PHP binary path and PHP file directory
✅ Zero External Dependencies - Uses your existing PHP installation
✅ TypeScript Support - Full type definitions included
Installation
Getting started is incredibly simple:
npm install astro-php-ssr
Or with your preferred package manager:
yarn add astro-php-ssr
# or
pnpm add astro-php-ssr
Prerequisites: You'll need php-cgi (preferred) or php installed on your system. Check with:
php-cgi -v
# or
php -v
Basic Usage
1. Configure Your Astro Project
First, add the integration to your astro.config.mjs:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import astroPhpSSR from 'astro-php-ssr';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [
astroPhpSSR({
phpDir: './php', // Where your PHP files live
phpBinary: 'php-cgi' // Or 'php' as fallback
})
]
});
2. Create Your PHP Directory Structure
your-project/
├── php/
│ ├── api/
│ │ └── hello.php
│ └── contact.php
├── src/
│ └── pages/
│ └── index.astro
└── astro.config.mjs
3. Write Your PHP Routes
Create a simple API endpoint at php/api/hello.php:
<?php
header('Content-Type: application/json');
$name = $_GET['name'] ?? 'World';
echo json_encode([
'message' => "Hello, $name!",
'timestamp' => time()
]);
4. Access Your PHP Routes
Your PHP files are automatically served under /php/:
http://localhost:4321/php/api/hello.php?name=Astro
Response:
{
"message": "Hello, Astro!",
"timestamp": 1698624000
}
Real-World Examples
Example 1: Contact Form Handler
Create a contact form handler in php/contact.php:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$message = $_POST['message'] ?? '';
// Validate
if (empty($name) || empty($email) || empty($message)) {
http_response_code(400);
echo json_encode(['error' => 'All fields are required']);
exit;
}
// Process (send email, save to database, etc.)
mail('admin@example.com', 'Contact Form', $message);
echo json_encode(['success' => true]);
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
Use it in your Astro component:
---
// src/pages/contact.astro
---
<form action="/php/contact.php" method="POST">
<input type="text" name="name" placeholder="Your Name" required />
<input type="email" name="email" placeholder="Your Email" required />
<textarea name="message" placeholder="Your Message" required></textarea>
<button type="submit">Send</button>
</form>
Example 2: WordPress REST API Bridge
Integrate WordPress content without exposing your WordPress installation:
<?php
// php/api/posts.php
$wp_url = 'https://your-wordpress-site.com';
$endpoint = '/wp-json/wp/v2/posts';
$response = file_get_contents($wp_url . $endpoint);
header('Content-Type: application/json');
echo $response;
Fetch in your Astro page:
---
// src/pages/blog.astro
const response = await fetch('http://localhost:4321/php/api/posts.php');
const posts = await response.json();
---
<div>
{posts.map(post => (
<article>
<h2>{post.title.rendered}</h2>
<div set:html={post.excerpt.rendered} />
</article>
))}
</div>
Example 3: Database Operations
Access your MySQL database using PHP's native drivers:
<?php
// php/api/users.php
$db = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$stmt = $db->query('SELECT id, name, email FROM users LIMIT 10');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
header('Content-Type: application/json');
echo json_encode($users);
Advanced Configuration
Custom PHP Binary Path
If your PHP installation is in a non-standard location:
astroPhpSSR({
phpDir: './php',
phpBinary: '/usr/local/bin/php-cgi'
})
Multiple PHP Directories
You can structure your PHP files however you like within the phpDir:
php/
├── api/
│ ├── v1/
│ │ └── users.php
│ └── v2/
│ └── users.php
├── admin/
│ └── dashboard.php
└── public/
└── info.php
Access them at:
/php/api/v1/users.php/php/api/v2/users.php/php/admin/dashboard.php/php/public/info.php
Performance Considerations
astro-php-ssr spawns a PHP process for each request using CGI. While this works great for:
- Low to moderate traffic sites
- Development environments
- Internal tools and dashboards
- API endpoints that don't need millisecond response times
For high-traffic production applications, consider:
- Caching PHP responses
- Using PHP-FPM for long-running PHP processes
- Rate limiting your PHP endpoints
- Offloading heavy computation to background jobs
Use Cases
This integration shines in several scenarios:
-
Legacy Integration: You have an existing PHP codebase and want to modernize the frontend without a complete rewrite
-
Gradual Migration: You're moving from PHP to a modern stack and need both worlds to coexist
-
PHP-Specific Libraries: You need functionality that's only available in PHP (certain payment gateways, legacy APIs, etc.)
-
Content Management: Integrating with PHP-based CMSs like WordPress, Drupal, or custom systems
-
Prototyping: Quickly spin up backend functionality using PHP's extensive ecosystem
Limitations & Considerations
- Requires PHP: Your deployment environment must have
php-cgiorphpinstalled - CGI Performance: Each request spawns a new PHP process (consider PHP-FPM for production)
- Session Management: PHP sessions work, but coordinate with Astro's session handling
- File Uploads: Supported through standard PHP
$_FILEShandling
Deployment
Deploying to Vercel/Netlify
These platforms don't support PHP out of the box. Consider:
- Using Vercel/Netlify for static Astro content
- Deploying PHP portions to a PHP-friendly platform
- Using serverless PHP options
Deploying to VPS/Traditional Hosting
This is where astro-php-ssr really shines:
- Ensure PHP is installed:
sudo apt install php-cgi - Build your Astro project:
npm run build - Run with Node:
node dist/server/entry.mjs
Works perfectly on DigitalOcean, Linode, AWS EC2, or any VPS with Node.js and PHP.
Roadmap
Future enhancements I'm considering:
- PHP-FPM support for better performance
- Built-in caching layer
- Request/response logging
- Hot reload for PHP files in development
- More granular error handling
Try It Yourself
Want to see it in action? Check out the live demo:
Or install it in your project:
npm install astro-php-ssr
Conclusion
astro-php-ssr bridges the gap between Astro's modern, performant frontend architecture and PHP's mature, extensive ecosystem. Whether you're integrating legacy code, gradually migrating to a modern stack, or just need PHP's specific capabilities, this integration makes it seamless.
The web development landscape doesn't have to be all-or-nothing. Sometimes the best solution is bringing the best of both worlds together.
Links
Have you used astro-php-ssr in your project? I'd love to hear about your experience! Drop a comment below or open an issue on GitHub.