Skip to content

Conversation

tBibaut
Copy link
Contributor

@tBibaut tBibaut commented Aug 26, 2025

Q A
Branch? 7.4
Bug fix? no
New feature? yes
Deprecations? no
Issues
License MIT

This PR allows a route to be defined as "static generated".

A static generated route is dumped on a storage thanks to a Symfony command: static-site-generation:generate.
With a simple NGINX / Caddy configuration, the generated files could be served directly.
Example with Caddy :

{$SERVER_NAME:localhost} {
	root * /app/public
	encode zstd gzip

	{$CADDY_SERVER_EXTRA_DIRECTIVES}

	try_files {path} /static-pages{path}.html
	try_files {path} /static-pages{path}

	php_server
}

A good use case for this feature is a blog, where pages almost never change.
In that case, if the route is configured as static generated, Symfony won't even load, as the server serves the generate page directly (which implies a huge performance boost).

Similar implementations can be found in Tempest, Astro, and many other frameworks.

Here is an overview of the feature architecture:
ssg

StaticPageUrisProvider lists all Route marked as static, those can be defined as follows :

#[Route(name: 'app_sitemap', path: 'sitemap.xml', methods: ['GET'], format: 'xml', staticGeneration: true)]
public function __invoke(): Response
// ...

staticGeneration configuration can be :

  • a boolean
#[Route(name: 'home', path: '/', staticGeneration: true)]
  • a static array
#[Route(name: 'restaurant_detail', path: '/restaurant/{city}', staticGeneration: [['city' => 'lyon'], ['city' => 'hanoi']])]
  • a service ID (tagged with routing.static_site.params_provider)
#[Route(name: 'product_detail', path: '/product/{slug}', staticGeneration: ProductSlugProvider::class)]

There are some extension points :

  • StaticPageUrisProviderInterface to provide static page by another mean than symfony/routing. A third party CMS system for example.
  • ParamsProviderInterface to resolve params of dynamic routes. For example, with a route /blog/{slug}. We can create a param providers to list all slugs from a database, or all Markdown files in a directory.
  • StaticPageDumperInterface to dump the pages content on another storage. Can be useful if we need to dump it to an S3 storage, to GitHub pages, ...

@carsonbot carsonbot added this to the 7.4 milestone Aug 26, 2025
@carsonbot carsonbot changed the title [Routing] [HttpKernel] [FrameworkBundle] Static Site Generation [FrameworkBundle][HttpKernel][Routing] Static Site Generation Aug 26, 2025
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this shouldn't belong to a third party package.
That'd be possible, isn't it? Or do you see any blocker?
If we want this in core, everything should be in HttpKernel IMHO.
There's too much in Routing currently to me. Nothing there would be better :)

* @param bool|null $stateless Whether the route is defined as stateless or stateful, @see https://symfony.com/doc/current/routing.html#stateless-routes
* @param string|string[]|null $env The env(s) in which the route is defined (i.e. "dev", "test", "prod", ["dev", "test"])
* @param string|DeprecatedAlias|(string|DeprecatedAlias)[] $alias The list of aliases for this route
* @param bool|array{params?: string|iterable<array<mixed>>}|null $staticGeneration The static generation configuration, params : route parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have $defaults for defining default route parameters
and we also have $stateless for identifying routes that could trigger static page generation
can't we rely on those? that'd reduce complexity IMHO

continue;
}

if ([] !== $route->getMethods() && !\in_array(Request::METHOD_GET, $route->getMethods(), true)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all comparisons to bool/empty values should be simplified in the PR - those are needless boilerplate:

Suggested change
if ([] !== $route->getMethods() && !\in_array(Request::METHOD_GET, $route->getMethods(), true)) {
if ($route->getMethods() && !\in_array(Request::METHOD_GET, $route->getMethods(), true)) {

$compiledRoute = $route->compile();

// $config "true" means no params to match the path variables
if ([] === $compiledRoute->getPathVariables() || true === $config) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

etc

Suggested change
if ([] === $compiledRoute->getPathVariables() || true === $config) {
if (!$compiledRoute->getPathVariables() || $config) {

throw new LogicException(\sprintf('Expected route "%s" to accept GET method, it accepts "%s" only.', $routeName, implode(', ', $route->getMethods())));
}

if (false === ($route->getDefaults()['_stateless'] ?? false)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (false === ($route->getDefaults()['_stateless'] ?? false)) {
if (!($route->getDefaults()['_stateless'] ?? false)) {

@tBibaut
Copy link
Contributor Author

tBibaut commented Aug 27, 2025

I'm wondering if this shouldn't belong to a third party package. That'd be possible, isn't it?

Sure, it already exists as Stenope Bundle
https://github.com/StenopePHP/Stenope

I think implementing the basic SSG feature in core could be useful for this kind of bundle.
@ogizanagi, what do you think ?

I also think SSG is a must-have feature for a modern web framework.

But that's totally up to you, you have a much better vision of Symfony's goals and future than me :D

If we want this in core, everything should be in HttpKernel IMHO.
There's too much in Routing currently to me. Nothing there would be better :)

I'll think about it, but some configuration are required, #[Route] felt like a good match.
Maybe a new HttpKernel\Attribute ?

@ro0NL
Copy link
Contributor

ro0NL commented Aug 27, 2025

implementing the basic SSG feature in core

in the long run it's minus "basic" ;)

i think it's a good feature; but im not yet convinced somehow staticGeneration is the best implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants