Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ssi Bundle Laravel Package

ongr/ssi-bundle

Symfony bundle that adds Server Side Includes (SSI) support, letting you compose pages from independently rendered fragments. Useful for caching dynamic blocks and integrating SSI-enabled web servers with Symfony controllers and templates.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else: scope check. This bundle is archived, last released 2014-10-, targets Symfony 2.x, and has zero dependents. Do not plan a greenfield integration around it. Run composer require ongr/ssi-bundle in a sandbox first — on Symfony 4+ the constraint resolution will almost certainly fail, and even if it installs, the bundle relies on the Symfony 2 AppKernel/services.xml conventions and the old fragment/templating layer. If you genuinely need it, vendor a fork and bump the constraints, or skip to the "use the core equivalent" note in Gotchas.

Where to look first

  1. README.md — historically thin for ONGR bundles; treat it as a hint, not documentation.
  2. Resources/config/services.xml (or .yml) — this tells you the real service IDs and, more importantly, which Twig extension class is registered with the twig.extension tag.
  3. Twig/ — find the extension and read the function signature. The exposed function name is the whole API surface; confirm whether it is ssi(), render_ssi(), or similar rather than trusting memory or blog posts.
  4. DependencyInjection/Configuration.php — the config tree is where any tunables live (base URL, route name, cache lifetime).
  5. Tests/ — the most reliable documentation in an archived repo; the assertions show the exact string format emitted.

Minimum wiring (Symfony 2 style)

// app/AppKernel.php
$bundles = [
    // ...
    new ONGR\SSIBundle\ONGRSSIBundle(),
];

Then a first use case: take one expensive, cacheable block on a page — a recommendations sidebar, a user menu, a "recently viewed" strip — and replace its inline Twig render() with the bundle's SSI function. The page shell then ships instantly and the web server stitches the fragment in at the edge.

{# before: rendered inside the PHP request #}
{{ render(controller('ShopBundle:Widget:recommendations', { userId: user.id })) }}

{# after: emits an SSI include the front-end server resolves #}
{{ ssi('shop_recommendations', { userId: user.id }) }}

The expected output is an HTML comment of the form <!--#include virtual="/path/to/fragment?..." -->. Verify by curl-ing the page and grepping for #include.

Implementation Patterns

Route-per-fragment (the pattern worth copying). SSI's virtual attribute is a URL, so the cleanest design is to expose each fragment as a real, routable controller action rather than pointing at Symfony's internal /_fragment endpoint. This gives you a URL that nginx/Varnish can cache by rule, that you can hit manually for debugging, and that survives independently of the parent page.

/**
 * @Route("/_fragments/recommendations/{userId}", name="shop_recommendations")
 */
public function recommendationsAction($userId)
{
    $response = $this->render('ShopBundle:Widget:recommendations.html.twig', [...]);
    $response->setSharedMaxAge(300);   // let the edge cache it
    $response->setPublic();
    return $response;
}

Layered caching. Treat SSI as a caching strategy, not a rendering trick. Parent page gets a short s-maxage or private; fragments get long s-maxage and are purged independently when the underlying data changes. This is the actual day-to-day win: you can invalidate "recommendations for user 42" without touching the page shell.

Graceful fallback per environment. Don't let developers see raw HTML comments in dev. Wrap the Twig call in a project-level helper that inspects a config flag and falls back to Symfony's inline fragment renderer in dev/test:

{% if ssi_enabled %}
    {{ ssi('shop_recommendations', { userId: user.id }) }}
{% else %}
    {{ render(controller('ShopBundle:Widget:recommendations', { userId: user.id })) }}
{% endif %}

Flip ssi_enabled in config_dev.yml off, config_prod.yml on. You lose exact production parity, so keep at least one staging environment with SSI actually enabled.

PHP-side composition. If you generate markup outside Twig, resolve the handler/URL-generator service and build the directive yourself — useful for email-safe or CMS-stored content where you assemble HTML strings.

Testing. Assert on the directive in a functional test rather than trying to render end-to-end (your test kernel cannot process SSI):

$this->assertContains('<!--#include virtual="/_fragments/recommendations/42', $client->getResponse()->getContent());

Separately, test the fragment route on its own to prove it renders and sets the headers you expect.

Gotchas and Tips

Consider not using this package at all. Symfony core has shipped SsiFragmentRenderer for years, enabled with framework: { fragments: ~, ssi: { enabled: true } }, consumed via {{ render_ssi(controller(...)) }}. It handles the /_fragment URL, the surrogate hash, and the escaping for you. The only real reason to reach for a third-party bundle is the route-per-fragment style above — and that is thirty lines of Twig extension you can own. With 2 stars, 0 dependents, and a 2014 release, the maintenance cost outweighs the saved effort.

The server must actually do SSI. Apache needs mod_include plus Options +Includes (or +IncludesNOEXEC); nginx needs ssi on; in the relevant location. PHP's built-in server (bin/console server:run) and most containerized dev setups do not process SSI — fragments will appear as inert HTML comments. Confirm with nginx -T / apachectl -M | grep include before debugging the bundle.

Ordering and buffering in nginx. The SSI filter must run before gzip, and ssi_last_modified/buffering settings affect whether subrequests are processed at all. If a fragment silently vanishes, check the directive isn't being gzipped away. Similarly, never set Content-Length on the parent response — SSI changes the body size after PHP is done.

Nested includes. Apache handles nested SSI but with recursion limits; nginx requires the subrequest location to also have ssi on; to process includes inside fragments. Keep nesting one level deep.

Do not let user data reach the directive. <!--#include virtual="..." --> and especially file="..." are file-inclusion primitives. Any parameter interpolated into the path must be validated/whitelisted; never pass a raw user-supplied string, or you have handed the web server to an attacker. Also strip/avoid the directive in any path where a user-controlled CMS field is echoed.

HTML minifiers and autoescaping. Twig autoescaping and asset minifiers can mangle or strip the HTML comment. Use |raw on the generated directive and exclude the fragment region from minification.

Query strings and server version. virtual with a query string is supported by modern Apache and nginx, but behaviour around re-encoding/escaping differs (nginx escapes & in some configurations). Prefer path segments over query strings for fragment parameters — one more argument for the route-per-fragment pattern.

Debugging recipe. curl -s http://host/_fragments/... to prove the fragment route works standalone, then curl -s http://host/page | grep '#include' to prove the directive is emitted, then curl -s http://host/page to prove the server expands it. Each step isolates one layer. Server-side, bump Apache LogLevel or watch nginx error_log for subrequest failures.

Extension points. The Twig extension and the URL-generating service are the two seams worth overriding: decorate or replace the URL generator to change how fragment paths are built (add a cache-busting token, switch to absolute URLs for a CDN edge), and subclass the extension if you need extra escaping or a different directive format. Check services.xml for the exact IDs — do not assume they are public and stable, because they are neither documented nor maintained.

Migration exit ramp. If you adopt this, isolate all calls behind your own Twig helper. That way, when the Symfony constraint finally blocks you, swapping the implementation for core render_ssi() or a hand-rolled directive takes one edit rather than a codebase-wide grep.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums