Enterprise-grade Symfony bundle for seamless React + Vite integration
A lightweight, secure, and high-performance bundle that brings modern React development to Symfony, replacing Stimulus with production-ready components.
Languages: 🇬🇧 English | 🇫🇷 Français
✅ Production-Ready - 152 tests, 88% coverage, 100% security hardening
✅ Performance Monitored - Built-in metrics, logging, and observability
✅ Enterprise Security - XSS protection, command injection prevention, SSRF validation
✅ Zero Configuration - Works out of the box with Symfony Flex
✅ Modern Tooling - Vite-powered HMR for blazing-fast development
✅ Highly Testable - Comprehensive test suite with edge case coverage
If this bundle saves you time, consider becoming a sponsor to support ongoing development and maintenance of this open-source project.
composer require julien-lin/react-bundle-symfonyComposer automatically installs npm dependencies via Symfony Flex.
mkdir -p assets/React/Components
touch assets/React/index.js// Export all your React components here
// export { default as MyComponent } from './Components/MyComponent';import React from 'react';
import { createRoot } from 'react-dom/client';
import * as ReactComponents from '../React';
// Auto-mount React components by data attribute
document.querySelectorAll('[data-react-component]').forEach(element => {
const componentName = element.dataset.reactComponent;
const props = JSON.parse(element.dataset.props || '{}');
const Component = ReactComponents[componentName];
if (Component) {
createRoot(element).render(<Component {...props} />);
}
});{% extends 'base.html.twig' %}
{% block content %}
{{ react_component('YourComponent', {
title: 'Hello React',
message: 'Welcome to production-ready React in Symfony'
}) }}
{% endblock %}
{% block javascripts %}
{{ vite_entry_script_tags('app') }}
{% endblock %}# Development (with HMR)
php bin/console react:build --dev
# Production
php bin/console react:build --prod✅ Done! Your React component is live.
- Installation
- Core Features
- Advanced Usage
- Production Deployment
- Configuration
- API Reference
- Performance & Monitoring
- Security
- Troubleshooting
- Contributing
composer require julien-lin/react-bundle-symfonyThe Composer installation script will automatically install npm dependencies.
-
The bundle registers automatically via Symfony Flex.
-
Configure the bundle in
config/packages/react.yaml:
react:
build_dir: 'build'
assets_dir: 'assets'- If npm dependencies were not automatically installed:
cd vendor/julien-lin/react-bundle-symfony
npm install- Create the file structure in your Symfony project (if it doesn't already exist):
# Create the folder for your React components
mkdir -p assets/React/Components
# Create the index.js file to export your components
touch assets/React/index.js- Configure
assets/React/index.js(entry point for your components):
/**
* Entry point for all React components in the project
* Export all your components created in React/Components/ here
*/
// Example:
// export { default as MyComponent } from './Components/MyComponent';
// Add your exports here as you go- Configure
assets/js/app.jsx(must import from../React):
import React from 'react';
import { createRoot } from 'react-dom/client';
// Import all your components from the index
import * as ReactComponents from '../React';
// ... rest of the code (usually already configured)Before using the bundle, make sure you have the following structure in your Symfony project:
assets/
├── React/
│ ├── Components/ # Create your components here
│ └── index.js # Export your components here
└── js/
└── app.jsx # Entry point (already configured)
{% extends '@React/react_base.html.twig' %}
{% block body %}
{# Use react_component with the exact name of your component #}
{{ react_component('MyComponent', {
title: 'My title',
message: 'My message',
count: 42,
items: ['item1', 'item2']
}) }}
{% endblock %}
{% block javascripts %}
{{ vite_entry_script_tags('app') }}
{% endblock %}Important: The component name in react_component() must match exactly the name used in the export of assets/React/index.js.
php bin/console react:build --devphp bin/console react:buildReactBundle/
├── src/
│ ├── ReactBundle.php # Main class
│ ├── DependencyInjection/ # Configuration
│ ├── Service/ # Services
│ ├── Twig/ # Twig extensions
│ ├── Command/ # Symfony commands
│ └── Composer/ # Composer scripts
├── Resources/
│ ├── config/
│ │ └── services.yaml
│ └── views/ # Twig templates
├── composer.json
├── package.json
└── vite.config.js
Create your React components in your Symfony project, not in the bundle:
your-symfony-project/
├── assets/
│ ├── React/
│ │ ├── Components/ # Your React components here
│ │ │ ├── MyComponent.jsx
│ │ │ ├── Navbar.jsx
│ │ │ └── ...
│ │ └── index.js # Centralized export of all components
│ └── js/
│ └── app.jsx # Entry point (imports from React/)
├── public/
│ └── build/ # Assets compiled by Vite
└── config/
└── packages/
└── react.yaml # Bundle configuration
1. Create the file → assets/React/Components/MyComponent.jsx
2. Export in index.js → assets/React/index.js
3. Rebuild assets → php bin/console react:build
4. Use in Twig → {{ react_component('MyComponent', {...}) }}
Create your component in assets/React/Components/YourComponent.jsx:
import React from 'react';
const YourComponent = ({ title, message, onAction }) => {
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h2>{title}</h2>
<p>{message}</p>
{onAction && (
<button onClick={onAction}>Action</button>
)}
</div>
);
};
export default YourComponent;Add the export in assets/React/index.js:
// ... other existing exports
// Your new component
export { default as YourComponent } from './Components/YourComponent';Important: The name used in the export (YourComponent) must match exactly the name you will use in Twig.
In your Twig template:
{% extends '@React/react_base.html.twig' %}
{% block body %}
{# Use the exact export name #}
{{ react_component('YourComponent', {
title: 'My title',
message: 'My personalized message'
}) }}
{% endblock %}
{% block javascripts %}
{{ vite_entry_script_tags('app') }}
{% endblock %}After creating or modifying a component:
# In development (with HMR)
php bin/console react:build --dev
# In production
php bin/console react:buildimport React from 'react';
const ProductCard = ({ name, price, image, onAddToCart }) => {
return (
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '20px',
textAlign: 'center'
}}>
<img
src={image}
alt={name}
style={{ width: '100%', borderRadius: '4px', marginBottom: '10px' }}
/>
<h3>{name}</h3>
<p style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#ff6b6b' }}>
${price}
</p>
<button
onClick={onAddToCart}
style={{
padding: '10px 20px',
backgroundColor: '#ff6b6b',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Add to cart
</button>
</div>
);
};
export default ProductCard;// ... other exports
export { default as ProductCard } from './Components/ProductCard';{% extends '@React/react_base.html.twig' %}
{% block body %}
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;">
{% for product in products %}
{{ react_component('ProductCard', {
name: product.name,
price: product.price,
image: product.image,
onAddToCart: '() => alert("Added to cart!")'
}) }}
{% endfor %}
</div>
{% endblock %}
{% block javascripts %}
{{ vite_entry_script_tags('app') }}
{% endblock %}- ✅ Create your components in
assets/React/Components/(in your project, not in the bundle) - ✅ Export them in
assets/React/index.jswith the exact name you will use in Twig - ✅ Name is case-sensitive:
ProductCard≠productcard≠Productcard - ✅ Props are passed as JSON: use simple types (string, number, boolean, array, object)
- ✅ JavaScript functions can be passed as strings (e.g.,
'() => alert("test")') - ✅ Rebuild after each modification:
php bin/console react:build(or--devfor HMR)
- Identify your Stimulus controllers
- Create equivalent React components
- Replace
data-controller="..."with{{ react_component(...) }} - Test individually
In config/packages/react.yaml:
react:
build_dir: 'build'
assets_dir: 'assets'You can define VITE_SERVER_URL in your .env to customize the Vite server URL in development:
VITE_SERVER_URL=http://localhost:5173Or in config/packages/react.yaml:
react:
vite_server: 'http://localhost:5173'- Check that
{{ vite_entry_script_tags('app') }}is present in your template - Check the browser console for JavaScript errors
- Make sure assets are compiled:
php bin/console react:build - Check that manifest.json exists in
public/build/.vite/
- Check that the component is exported in
assets/React/index.jsof your Symfony project - Check that the name in the export matches exactly the name used in Twig (case-sensitive)
- Check that the component file exists in
assets/React/Components/ - Check that you have rebuilt the assets:
php bin/console react:build - Check the browser console to see the list of available components
- Check that the Vite server is started:
php bin/console react:build --dev - Check that port 3000 (or the configured one) is not in use
- Check the configuration in
vite.config.js - Check that
VITE_SERVER_URLis correctly configured
- Check that Node.js >= 18.0.0 is installed:
node --version - Check that npm is installed:
npm --version - If you use nvm, make sure the environment is correctly loaded
- The bundle now supports Windows with
DIRECTORY_SEPARATOR - If you encounter problems, check folder permissions
- Make sure paths in
vite.config.jsare correct
To add npm packages (like react-icons, axios, etc.) to your project:
-
Install the package in your Symfony project root (not in the bundle):
npm install react-icons
-
Import and use it in your components:
import { FaGithub } from 'react-icons/fa';
-
Rebuild assets:
php bin/console react:build
📖 Full guide: See ADDING_NPM_PACKAGES.md for detailed instructions and examples.
- Complete documentation: see
QUICKSTART.md - Installation guide: see
INSTALLATION.md - Adding npm packages: see
ADDING_NPM_PACKAGES.md - Report a bug: GitHub Issues
- Become a sponsor: GitHub Sponsors
MIT