r/symfony

I built a SPA router for SonataAdmin (Symfony) that requires zero backend changes — @wlindabla/sonata_spa [OSS]

Reddit — r/symfony + r/typescript + r/webdev



Hey r/symfony,

Long-time SonataAdmin user here. I finally got fed up with full page reloads on every navigation and built a proper fix.

TL;DR: @wlindabla/sonata_spa is a TypeScript library that turns your existing SonataAdmin backend into an SPA — sidebar clicks, pagination, filters, sorting, show/delete — all handled without full reloads. Zero changes to your Symfony controllers or Twig templates.

yarn add @wlindabla/sonata_spa

> Requires: AdminLTE >= 3.x / 4.x · Bootstrap >= 5.3 · SonataAdmin >= 4.x · Node.js >= 18


Why I built this

SonataAdmin is genuinely excellent. But by default, every navigation is a full page reload — every script re-executes, every Bootstrap 5 component and every Stimulus controller re-initializes from scratch. For an internal admin panel used daily by your team, this friction is real.

Most "fixes" either:

  • Replace Sonata entirely with a React frontend (lose everything Sonata gives you)
  • Use Turbo (conflicts with Sonata's Stimulus outlet system — uniqid() IDs change on every request)
  • Patch individual pages with custom AJAX (unmaintainable mess)

None of these are satisfying. I needed something that preserves 100% of Sonata's features while eliminating full reloads.


The architecture — Symfony concepts in TypeScript

The library is directly inspired by Symfony's HttpKernel and EventDispatcher:

User clicks link
  → BindingManager builds SpaRequest
  → SpaKernel.handle(request)
      1. dispatch spa:request        ← STOPPABLE
      2. RequestMatcher — server-managed? → full reload if yes
      3. RouteResolver.resolve(url)  → RouteMatch
      4. dispatch spa:route:resolved ← STOPPABLE
      5. dispatch crud:list / crud:show / crud:delete / ...
          → Subscriber: fetch → DOM swap → history → Stimulus reconnect → dom:ready

Minimal setup

// assets/spa.ts
import { SpaKernel, SpaEvents } from '@wlindabla/sonata_spa';
import { BrowserEventDispatcher } from '@wlindabla/event_dispatcher/browser';

document.addEventListener('DOMContentLoaded', () => {
    const spa = SpaKernel.create(
        {
            router: {
                sidebarSelector:           '#app-sidebar',
                mainSelector:              '#app-main',
                mainContentAreaSelector:   '#app-content',
                mainContentHeaderSelector: '#app-content-header',
            },
        },
        'prod',
        new BrowserEventDispatcher(window, { bubbles: true })
    );

    // Re-initialize your libraries after each swap
    spa.getDispatcher().addListener(SpaEvents.DOM_READY, (event) => {
        initMyLibraries(event.container);
    });

    spa.boot();
});
{# In your standard_layout.html.twig #}
{% block javascripts %}
    {{ parent() }}
    {{ encore_entry_script_tags('spa') }}
{% endblock %}

That's the full setup for basic usage.


Key features

  • Surgical DOM swaps — only the changed parts of the page are replaced. List pages swap only the data table + filters. Show/dashboard pages swap the full #app-main.
  • Correct Stimulus reconnectionDomManager detects new Stimulus outlet IDs after each swap and forces reconnection via requestAnimationFrame.
  • CSRF-aware delete flow — fetches the Sonata delete confirmation page, extracts the CSRF token, then POSTs with X-Requested-With: XMLHttpRequest. SweetAlert2 modal instead of browser confirm().
  • Two-step batch flow — POSTs the batch form, parses the Sonata confirmation HTML, shows a SweetAlert2 modal, then re-POSTs with confirmation=ok.
  • History APIpushState with RouteMatch stored in the state object. Popstate (back/forward) replays navigation without re-resolving the URL.
  • Extension system — modeled after Sonata's AdminExtensionInterface. Add custom route patterns, binding managers, subscribers, and server-managed URL rules without touching the sealed kernel.
  • Full TypeScript — strict types, exactOptionalPropertyTypes: true.

Override the default confirmation UI

// Replace SweetAlert2 with your own modal — priority > 0 overrides the default
dispatcher.addListener(
    SpaEvents.DELETE_CONFIRM_REQUESTED,
    async (event) => {
        const confirmed = await myModal.confirm(event.title, event.message);
        if (confirmed) {
            await event.accept(); // proceeds with DELETE POST
        } else {
            event.cancel();
        }
    },
    10 // higher priority than the built-in default (0)
);

What stays server-managed (full reload)

By design, /create and /edit URLs always trigger a full reload. These pages require server-side CSRF token generation and Sonata's session management. Form submissions are handled via SPA (JSON response), but the initial page load is always a full request.

You can add more server-managed patterns in the options:

serverManagedUrlOptions: [
    /\/export(\?.*)?$/,
    /\/import(\?.*)?$/,
]

Links

Happy to answer questions about the implementation — especially around the Stimulus reconnection logic and the CSRF flow, which were the trickiest parts to get right.


Built with ❤️ in Benin 🇧🇯 — INTERNATIONALES WEB APPS & SERVICES

u/Cute_Bag_3942 — 4 days ago
▲ 65 r/symfony+2 crossposts

Composer 2.9.8 and 2.2.28 fix GitHub Actions token disclosure in error messages

Please immediately update Composer to version 2.9.8 or 2.2.28 (LTS) by running composer.phar self-update. The new releases fix a vulnerability where Composer leaks the full contents of GitHub Actions issued GITHUB_TOKENs or GitHub App installation tokens to the GitHub Actions logs. GitHub introduced a new format for these tokens including a - (hyphen). The new format is gradually being rolled out to repositories. The new format fails Composer’s validation, leading to an error message that exposes the full token contents to stderr. A CVE identifier will be assigned and added to this post once available.

blog.packagist.com
u/naderman — 9 days ago

What are your favorite Claude Code skills?

Hey devs,

What are your favorite Claude Code skills?

Whether it's for quality audits, security audits, refactoring, testing, or anything else — I'd love to hear what you've found useful in your Symfony workflow.

Feel free to share links to repos or your own custom skills.

Thanks!

reddit.com
u/SMB-Punt — 9 days ago

FormsBundle - Release

I've always had a distaste for writing `FormType` classes, so much so that I just stopped using them. But then I had to deal with raw HTML forms, and I hated that even more, so I decided to automate the boring part.

FormsBundle generates Symfony forms directly from DTOs: property types, nullability, and validator constraints already encode everything a `FormType` needs, the bundle just reads them, builds the form type class, and caches it.

Link: https://github.com/n-fasano/FormsBundle

Feedback welcome!

u/semdens — 11 days ago

AssetMapper seems to not detect changes for CSS when using Docker (dunglas)

I use Dunglas symfony docker to run the app (on dev environment)

Installed asset mapper, everything is setup correctly by flex.

I update CSS file but the version of css exposed to browser stays the same and therefore no changes are reflected. Anyone had this issue?

I remember using simple symfony serve it worked fine.

reddit.com
u/akimbas — 13 days ago
▲ 11 r/symfony

[Showcase] OrderInvoiceBundle: Symfony bundle to manage orders and invoices without full e-commerce overhead.

If you don't need a full-blown e-commerce store but just need to handle orders and generate invoices (PDF, proforma, etc.) in your Symfony app, I've created a bundle to simplify this: https://github.com/DavidPetrasek/OrderInvoiceBundle

----------------------------------------------

I am aware Sylius provides standalone Components & Bundles like https://github.com/Sylius/SyliusOrderBundle

... but they're outdated and can't find them in their new docs. Sylius also doesn't seem to support invoice types: regular, proforma, advance and final

u/RefrigeratorHairy256 — 13 days ago