Refactoring front-end

The current web site was created before the release of Symfony 2.0 back in July 2011. Although the code is continuously updated to the most recent stable version (Symfony 3.3 at the time of writing this blog post), the application is showing its age in some parts. That's why we decided to revamp its front-end simplifying the templates and managing the web assets differently.

Our front-end needs are simple, so we use a pretty traditional setup based on Bootstrap 3 and a bunch of SCSS and JavaScript files. This worked well for us at the beginning, but it was becoming harder and harder to maintain lately.

The refactoring process took us almost two weeks and involved 50 commits changing 219 files (mostly .html.twig and .scss). We added or changed 6,209 lines of code and removed 10,291 lines. In this article, we explain some of the most relevant changes made during the refactorization.

New asset organization

Previously, we had tens of small SCSS files divided by their purpose: code.scss, typography.scss, forms.scss, etc. Besides making it hard to reuse styles, this approach complicates maintenance because it's hard to find all the styles involved in the design of a given page element.

This is a typical developer error: splitting something into lots of smaller pieces believing that this "modular" design is better, but ending up with a hard to maintain mess.

Now we define all the common styles in a big app.scss file and we have dedicated files for pages with special needs: home.scss, download.scss, etc. This makes the design massively simpler to maintain and helps us creating a more consistent design, because it's easier to reuse the same styles for different elements.

New design philosophy

The previous design was "Desktop first" and the new one is "Mobile first", which is something that we wanted to change since a long time ago. Any feature is now designed for and tested on smartphones first, and then we adjust things for larger devices if needed.

The result is that contents now adapt nicely to any device. For example, the Symfony Roadmap page, where you can find information about the current and upcoming Symfony versions, now shows a vertical roadmap on smartphones and a horizontal roadmap on larger devices. See the before/after comparison of this page:

In order to avoid complicating the design too much, we decided to define just two responsive breakpoints: 768px for tablets and small desktops and 992px for the rest of devices.

New CSS styles

Previously, we didn't use any specific CSS methodology and most of our selectors relied on nested HTML id attributes (e.g. #comments #add-comment). The new design uses HTML class attributes exclusively and it's based on the BEM methodology. We don't apply BEM strictly because it can rapidly become too verbose, but BEM has helped us creating a more modular and easier to maintain design.

Another nice improvement was including third-party dependencies in a more granular fashion. Instead of including the entire Bootstrap 3 framework, we now pick the exact Bootstrap files that we need:

// app.scss
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/mixins";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/normalize";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/grid";
// ...

The last change that allowed us to simplify styles a lot are the utility CSS classes that set the margin properties (e.g. .m-t-0 means margin-top: 0, .m-b-15 means margin-bottom: 15px, etc.)

Although they are a bit controversial and some people think that they can bloat your CSS, in our case we covered all our needs with just 10 utility classes, which in turn saved us lots of useless custom CSS classes that only set margins or paddings. These utility classes are coming to Bootstrap 4 too.

New workflow

At the beginning, we used Assetic to manage assets. However, a few months ago, we removed it and started the transition to JavaScript-based asset management. As most non-JavaScript developers, we were confused by the amount of tools available, but at the end we settled on using Webpack.

Webpack is a nice tool to bundle your styles, scripts and images, process them and generate the final CSS and JavaScript files. However, at first Webpack is tough to grasp. Luckily, we had an ally: Ryan Weaver. During the past months, Ryan has been secretly working on a new JavaScript tool to manage web assets.

This new tool, called Webpack Encore, is a simpler way to integrate Webpack into your application. It wraps Webpack, giving you a clean & powerful API for bundling JavaScript modules, pre-processing CSS & JS and compiling and minifying assets.

We've been using this tool in production on for the past couple of months and I must say that it's a delight to use. Moreover, this new tool will become the officially recommended way to manage assets on Symfony applications. Do you want to use it in your own projects? You won't have to wait much longer because it will be published this week.

New Twig templates

The previous Twig templates were pretty good, but we made some changes to them to simplify things using modern Twig features. These are some of the tricks we used and which you can use in your own projects too:

Null coalesce operator: introduced in Twig 1.28, it provides the same ?? operator as defined by PHP 7. It's a nice and concise replacement of the default filter:

{% set version = version_label ?? version_number ?? 'current' %}

{# equivalent to: #}
{% set version = version_label|default(version_number)|default('current') %}
{# also equivalent to: #}
{% set version = version_label is defined ? version_label : ... %}

Don't split templates into lots of fragments: splitting templates into tiny fragments and using include() to include them in the template can hurt performance. It also complicates maintenance, because it's harder to find where the contents are defined.

Create fragments only when some part of a template is truly reused in several templates. When including fragments, prefer the include() function to the include tag and always use the Twig namespace syntax, which is faster than the traditional bundle syntax:

{# the recommended way to include template fragments #}
{{ include('@App/blog/_list_comments.html.twig') }}

{# this bundle notation makes the application slower #}
{{ include('AppBundle:blog:_list_comments.html.twig') }}

Check for block existence: another feature added in Twig 1.28 is the support of is defined operator for blocks, which is useful to check for their existence in highly dynamic templates:

{% if block('intro') is defined %}
        {{ block('intro') }}
{% endblock %}

Define custom Twig namespaces: during the redesign, we replaced a custom icon font with proper SVG files for each icon. Referring to those files in templates is boring (e.g. images/icons/arrow.svg, bundles/blog/images/icons/arrow.svg) so we used custom Twig namespaces to store all icons under the icons namespace and embed them using the source() Twig function:

{# Twig namespaces create concise and beautiful templates #}
<i class="icon">{{ source('@icons/arrow.svg') }}</i>

Don't care about white spaces in HTML code: our work as developers is to create maintainable Twig templates, not to generate perfect looking HTML code. HTML is consumed by browsers not users, and it's mangled, minified and compressed before delivering it to the browser, so never mind about it:

{# this is beautiful and easy to maintain #}
<li class="{{ current == item.slug ? 'selected' }}" ...>

{# this is a mess and complicates everything for no good reason #}
<li{% if current == item.slug %} class="selected"{% endif %} ...>

The result

Combining all the changes and techniques explained above, the result of the refactorization was amazing. The web site looks and feels the same, but all the design issues are gone, the site is fully responsive and "mobile first", and the performance has improved dramatically: before, every page downloaded a 194KB app.css file (before gzipping it); now, the common app.css file weights just 59KB, a whopping 70% decrease!

Although the purpose of the refactoring wasn't to change the visual design of the site, we took this opportunity to make some minor changes, especially on the documentation section. For example, notes, tips and warnings now are easier to recognize:


I'm a bit sad about one thing only:
"Ryan has been secretly working on a new JavaScript tool to manage web assets.
This new tool is a simpler way to integrate Webpack into your application."

Another "new JS tool", once again, and again, and it never ends :(
A Webpack wrapper, nice, but why not a Webpack *bootstraper* with nice and cool documentation inside?
What were you thinking exactly when you chose to "create another JS tool" instead of "using a famous JS tool"?

I mean, a long time ago when I discovered Gulp, I found it to be very boring to setup because we had to write our tasks manually, it was a pain in the ass. I tried to create a Gulp wrapper ( ) but actually it consumed a lot of performances, and was not easy to setup either.
My solution was instead to propose an already setup Gulp configuration suitable for most websites ( ) that I've been using for lots of projects since, as it's based on a single config array, and it fits "most" web apps needs. And still, you can add your custom Gulp tasks in a blink of an eye because, hey, it's basically a Gulpfile, so just write in it and you're set.

I'm not a Webpack user, but the first glance of it made me fear about the future of frontend setup bootstraping: Webpack seems over-complicated *at first sight*, so if we have a "Webpack wrapper" I'm afraid we'll see users using this "new JS tool" and be lost whenever a Webpack error occurs because everything is run "behind the scenes". That's what a wrapper does.

This is a "hot-reaction", but maybe I'll be convinced by your tool when it releases during the week :)
@Alex, I recommend you to wait until the tool is released before ranting about it :)

Meanwhile, I can tell you that the tool is simple to setup, a delight to use, it has great documentation, super useful error messages, etc.
Great then! Can't wait to see what it looks like and if it's really a game changer :)
Hmm, sounds a lot like
@Tony Indeed - inspired by Mix (as you'll see in the library's README) - must a bit more "standard" to webpack's features and terminology (trying to be just a lightweight layer that builds on top of the best-practice tool).

@Alex I agree with what you said! The new tool will simply help generate Webpack's standard configuration format... and then Webpack will handle everything normally. We really wanted to stay thin and let Webpack do all the work (we even use all the same wording & terminology as webpack - we don't want to abstract you away from it).

Anyways, I hope you'll like it when it's released :).

@Ryan Weaver: Very curious about this new tool - where it will be announced? On Symfony blog or/and somewhere else?
I've already notices few changes in docs. I must say, I really like it.
Feels Symfony is alive on every front :)
@Michal, the tool has already been published, so I've updated the article to mention it. It's called "Webpack Encore" and it's available at
One thing I don't understand is why you should add logic between the html brackets at all. Drupal 8 provides an Attribute class that allows you to centralize the attributes for a tag. It has no connection with the Drupal core, so I could isolate the functionality. You can find it at
@David thanks for mentioning that and for letting me know about the Attribute class. Although I think it's a valid approach, personally I don't like it. For simple cases it adds a lot of verbosity and for complex cases it lets you add too much logic in the templates.

I prefer to inline the simple logic with HTML tags and move complex logic out of Twig to the PHP controllers or services. Thanks!
One thing I always felt a bad frontend experience in the documentation section is that the table of content stays not sticky but scrolls away. I thought about opening a ticket in the docu repository but i feel, this is more frontend layout related, or am I wrong?

besides that, thank you for the rework and explanations!
"so we used custom Twig namespaces to store all icons under the icon namespace and embed them using the source() Twig function"

I think this is a typo in "icon namespace". The example below uses @icons namespace.
@Vladimir nice catch! Fixed. Thanks.
Login with SensioLabsConnect to post a comment