Production Build & Deployment

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Ok team: just one more thing to talk about: how the heck can we deploy all of this to production?

Well, before that, our files aren't even ready for production yet! Open the public/build/ directory. If you open any of these files, you'll notice that they are not minified. And at the bottom, each has a bunch of extra stuff for "sourcemaps": a bit of config that makes debugging our code easier in the browser.

Building For Production

We get all of this because we've been creating a development build. Now, at your terminal, run:

yarn build

This is a shortcut for yarn encore production. When we installed Encore, we got a pre-started package.json file with... this scripts section:

30 lines package.json
... lines 2 - 19
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production --progress"
... lines 26 - 28

So, the real command to build for production is encore production, or, really:

./node_modules/.bin/encore production

Anyways, that's the key thing: Encore has two main modes: dev and production.

And... done! On a big project, this might take a bit longer - production builds can be much slower than dev builds.

Now we have a very different build/ directory. First, all of the names are bit obfuscated. Before, we had names that included things like app~vendor, which kind of exposed the internal structure of what entry points we had and how they're sharing data. No huge deal, but that's gone: replaced by these numbered files.

Also, if you look inside any of these, they're now totally minified and won't have the sourcemap at the bottom. You will still see these license headers - that's there for legal reasons, though you can configure them to be removed. Those are the only comments that are left in these final files.

And even though all the filenames just changed, we instantly move over, refresh, and... it works: the Twig helpers are rendering the new filenames.

Free Versioning

In fact, you may have noticed something special about the new filenames: every single one now has a hash in it. Inside our webpack.config.js file, this is happening thanks to this line: enableVersioning():

... lines 1 - 2
... lines 4 - 45
// enables hashed filenames (e.g. app.abc123.css)
... lines 48 - 76
... lines 78 - 79

And check it out, the first argument - which is a boolean of whether or not we want versioning - is using a helper called Encore.isProduction(). That disables versioning for our dev builds, just cause we don't need it, but enables it for production.

The really awesome thing is that every time the contents of this article_show.css file changes, it will automatically get a new hash: the hash is built from the contents of the file. Of course, we don't need to change anything in our code, because the Twig helpers will automatically render the new filename in the script or link tag. Basically... we get free file versioning, or browser cache busting.

This also means that you should totally take advantage of something called long-term caching. This is where you configure your web server - like Nginx - to set an Expires header on every file it serves from the /build directory with some super-distant value, like 1 year from now:

server {
    # ...

    location ~ ^\/build\/ {
        expires 365d;
        add_header Cache-Control "public";

The result is that, once a user has downloaded these files, they will never ask our server for them again: they'll just use their browser cache. But, as soon as we update a file, it'll have a new filename and the user's browser will ask for it again. It's just free performance. And if you got a step further and put something like CloudFlare in front of your site, your server will receive even less requests for your assets.


Now that we have these, optimized, versioned files, how can we deploy them up to production? Well... it depends. It depends on how sophisticated your deployment is.

If you have a really simple deployment, where you basically, run git pull on production and then clear the Symfony cache, you're probably going to need to install node on your production server, run yarn install, and then run yarn build up on production, each time you deploy. That's not ideal, but if you have a simple deployment system, that keeps it simple.


We show this on practice in our Animated Deployment with Ansistrano course.

If you have a slightly more sophisticated system, you can do it better. The key thing to understand is that, once you've run yarn build, the only thing that needs to go to production is the public/build directory. So you could literally run yarn build on a different server - or even locally - and then just make sure that this build/ directory gets copied to production.

That's it! You don't need to have node installed on production and you don't need to run anything with yarn. If you followed our tutorial on Ansistrano, you would run yarn wherever you're executing Ansistrano, then use the copy module to copy the directory.

More Features

Ok, that's it! Actually, there are more features inside Encore - many more, like enabling TypeScript, React or Vue support. But getting those all going should be easy for you now. Go try them, and report back.

And, like always, if you have any questions, find us in the comments section.

All right friends, seeya next time.

Leave a comment!

  • 2020-04-19 weaverryan

    Hey mickael lutin!

    You mentioned:

    > the design of my site is not the same before and after i run this command

    Can you explain this more? If all of your CSS is being processed through Encore, I would expect that your site would basically be unstyled before executing Encore and styled after. Or, are you saying that your design looks different between running yarn dev versus yarn build?

    Let us know!


  • 2020-04-17 mickael lutin

    hi thank you for the reply,
    I have no errors in any of the consoles, i have the message who say the build is successfully but no other bad messages.

  • 2020-04-17 Diego Aguiar

    Hey mickael lutin

    What's the output you get when running yarn build? Do you get any error in Browser's console?


  • 2020-04-17 mickael lutin

    i have a problem i can't resolve when i run "yarn buil", the design of my site is not the same before and after i run this command, can you help me please?

  • 2020-03-27 Kakha Kashmadze


  • 2020-03-26 weaverryan

    Hey Jacob Proulx!

    Great information here - I would *love* to have something like this work out-of-the-box, as I'm sure it would normally require quite a lot of setup to get working. But if you *could*, what an awesome optimization. It looks like 2 separate parts:

    A) The lazy-loading part
    B) The "responsive image" part where you have multiple image sizes

    Part (A) I think can't be done by Encore because it's up to your JavaScript to be loading those images lazily. That's not a good or bad thing - this is a fairly common problem to solve. But solving it would be different in a "traditional" server-side-loaded app vs React vs Vue, for example.

    Part (B) *would* be something you could with with Webpack. But, I see very little support for this. I only find these 2 Webpack modules - and - the second is *fairly* popular, but hasn't had a release in 15 months (that's a decade in Node land). I was surprised by that. So, Encore could in theory integrate that second library... but we try to leverage best practices instead of "forging our own way". I'm just simply surprised that there's not more conversation on this topic. I wonder if you have any thoughts on this.

    > By the way, I live in Canada and as you said in a previous class, poutine is great ;)

    Ha! SO glad you caught that - I've had the pleasure to enjoy it *just* a few times... but what's not to like? :D


  • 2020-03-25 Jacob Proulx

    Hi Ryan,

    I really appreciate that you took the time to reply even with these busy days ;) . And thanks for sharing the pull request, that is one important part of the optimisation I was thinking.

    For the lazy-load part, it would be 3 important points :
    1 - As you already mentioned, it would load the images on demand.
    2 - Matrix images should be pre-processed in multiple sizes (mostly mobile, tablets, laptops and desktops for a large image.) In my opinion, it should be the client side to decide which image size to download rather than the server side, because JavaScript can determine a screen size and and server cannot. This is why the first images to load on the screen also need the lazy-load part: JavaScript need to check the screen size before asking the server for the right image. In this point, I use Grunt to build and minify every images in each sizes when adding them to a project. I use Bootstrap sizes to be constant.
    3 - Because webp is always lighter than others matrix images types, each matrix images should have a webp version and its original for compatibility. Again, JavaScript is better to know if the client can read webp images rather than the server, so the lazy-load script can ask for the right image type.

    In short and to give an example, a large image would have 9 versions of itself : image.original.jpg, image.xs.jpg, image.xs.webp, image.s.jpg and so on. A user can now download the right image size and type for his browser at the right time to save loading time and bandwidth!

    I want to mention one last point: the reason I felt lazy-load images was missing to your tutorials, appart hearing you telling awesome jokes, giving us great advice, for all the extra-learning and making programming super fun (I can hear people telling me that I am a “lèche-botte”), is that Webpack Encore is a powerful tool and it is already ahead for optimisation in many ways, so it would be a huge part missing to not include image process.

    One last thing: it is by using the Chrome Audits tool that I tweaked my client-side optimisations knowledge. I think sharing this super cool tool could be also very nice for the other developers :D .

    I hope you’ll find this topic interesting! As I already mentioned, keep the great job. By the way, I live in Canada and as you said in a previous class, poutine is great ;)

    -Your fan, Jacob.

  • 2020-03-24 Diego Aguiar

    Hey Anton Bagdatyev

    If you go to the course overview or click on the chapter's list field on this page, you will see that all the chapters that you have fully watched has a little arrow to its left, if you see any without it, it means that you didn't finish watching it. You've to watch them all in order to get your certificate of completion.


  • 2020-03-24 Anton Bagdatyev

    Thank you very much for this tutorial!

    I have watched all the chapters, but after finishing this last chapter, the website says:

    You've reached the end!
    Looks like you skipped some chapters or challenges,
    finish the course to earn your certificate.

    If I click the Finish button, I am redirected to the course's index page, but I do not understand what I am missing...


  • 2020-03-19 weaverryan

    Hey Jacob Proulx!

    I really appreciate the nice words *and* the feedback / idea ❤️. About the lazy-load optimized image, I *do* think that image optimization would have been a good topic to talk about. We have a pull request to add support to Encore directly - - but I need to actually review it - things are especially busy these days ;). But can you tell me more about the lazy-load part? Are you referring to when images are loaded on-demand (e.g. as the user scrolls near them)? Or something else? I just want to make sure :).


  • 2020-03-18 Jacob Proulx

    Hi Ryan, thanks alot for all these super tutorials and being such a nice teacher. I used to be a Grunt fan before knowing about encore webpack and now I can’t wait to use it! With all the documentation out there and all the information you gave us, I know that it is possible for me and every one to build a project for our needs, but I felt that something is missing in your tutorials about encore and it is the image processing. For example, having a responsive lazy-load optimised image javascript module or something like that would have be more than awesome. I’m saying this because of all the extra learning we get following your tutorials that are priceless time savers and let’s not forget all the line of thought you tell us including the pros, cons and conclusions ;) Anyway, keep the great job! -Jacob

  • 2020-01-20 weaverryan

    Hey Félix BE TRONG!

    Sorry for the slow reply! This is not a nice error! Is this *all* the output that you see? It looks like it doesn't actually contain the error :/. Are you able to look into the log file to see more information?

    > All my files in public\build (app.js / app.css / runtime.js) have no hash

    About this part, you will only have the versioning hashes when doing the "production" build of encore. The error above IS using the production built, but in case you're running a command locally, just double-check that you're running a production build if you want to see the version hashes. You should also have a line like this in your webpack.config.js file:

    # ...


  • 2020-01-14 Félix BE TRONG

    Hello everybody. I have a problem when i run npm build. All my files in public\build (app.js / app.css / runtime.js) have no hash.
    I can't deploy on Heroku, the error message in the log is :

     [webpack.Progress] 100% 
    * ./pages/Homepage in ./assets/js/app.js
    Entrypoint app = runtime.e468f142.js 0.6905f39e.css 0.84d563c1.js app.9d089ddf.css app.9edd022c.js
    npm ERR! code ELIFECYCLE
    npm ERR! errno 2
    npm ERR! @ build: `encore production --progress`
    npm ERR! Exit status 2
    npm ERR!
    npm ERR! Failed at the @ build script.
    npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
    npm ERR! A complete log of this run can be found in:
    npm ERR! /tmp/npmcache.W1Cld/_logs/2020-01-14T14_40_16_410Z-debug.log
    -----> Build failed

    I need more help. Thank you.

  • 2019-09-04 Diego Aguiar

    Ah, of course you will have access to that file because it's on the public directory. If you enable webpack versioning

    // webpack.config.js

    then it would be very difficult to guess the file name. Or, another strategy is to manage those assets as private resources. For this you need to implement a route that will return the asset content after checking roles.

    // some_template.html.twig
    <script src="{{ path('admin_asset', {name: 'asset_name.js'}) }}"></script>

    I hope this give you an idea of what you have to do in case you want to manage your assets as private.

    Oh, and of course, you will have to move out the asset from the public directory and put it on a private directory like project_root/assets/admin


  • 2019-09-04 sheepwall

    I think I understand what you mean. I am already extending my admin.html.twig (where I have linked admin.js and admin.css) with my admin pages. The "problem" is that I can log out and still access the route to the asset and view them: i.e. is still accessible when not logged in.

    Maybe this is an apache/nginx problem, but I'm wondering if there is some nifty Symfony solution even here?


  • 2019-09-02 Diego Aguiar

    Hey sheepwall

    Thanks for your kind works :)
    What I think you can do is to create an "admin" layout where you import the "main" assets that are used across the site and import the admin assets as well, but you will use that layout only on your admin pages. Does it makes sense to you?


  • 2019-08-30 sheepwall

    Hello! Thank you so much for this tutorial series. Made JS and CSS feel so much better to develop.

    I have a question regarding assets used for admin routes. Is it possible to make assets available only to certain "ROLE_"s? It might not be critical information in the files, but I would rather as little as possible of the admin source be public.


  • 2019-06-15 Dirk J. Faber

    understood, thank you!

  • 2019-06-12 Diego Aguiar

    Hmm, I don't know a way to pass variables to a css file, probably you will have to keep those styles inlined in your template

  • 2019-06-11 Dirk J. Faber

    Cheers ;) I'll give it a try. Is there also a solution for a template variable in CSS? For example I would use in my "'in-page-css":

    background: url('{{ some_variable }}');

  • 2019-06-11 Diego Aguiar

    Hey Dirk J. Faber

    If you need to pass a template's variable into a JS "entry point" you can do it via window object. We usually set some things on it (window object) and then on a js script, we just read it window.myData.value


  • 2019-06-11 Dirk J. Faber

    While learning about Webpack Encore, also getting an understanding of Babel and PostCSS. Thanks a lot for creating Webpack Encore and this extensive tutorial which is very helpful.

    I have one use case where I still don't know if I can (and should) use Webpack, which is whenever I need a 'php-variable' in my css or js. Right now what I do in those cases is create a <style> or <script> tag within the Twig template where I can use my css or js and also my php-variables. Perhaps this could also move to Webpack? I can't really think of how to go about this though.

  • 2019-06-05 Victor Bocharsky

    Awesome! Thanks for replying back that it solved the issue ;)


  • 2019-06-04 Hessuss Hesiu Kowalski

    Hey Victor, it works, deprerror was gone :) thanx so much :) Cheers!

  • 2019-06-04 Victor Bocharsky

    Hey Hessuss,

    Hm, looks like something in your code triggers that deprecation message. Do you have more context in your console about it? Like file / line in file where it was triggered? Probably it comes from $.ajax() call, this might be helpful for you:


  • 2019-06-01 Hessuss Hesiu Kowalski

    Hi Ryan, i have a trouble. I use Bootstrap modal and .load() function to fill content modal window. When in remote content is encore_entry_script_tags i have in browser console error :

    [Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience.

    What i do wrong ?
    MY CODE :


    // modal window
    <div class="modal fade" id="modal_ajax" tabindex="-1" role="dialog" aria-labelledby="modal_ajaxTitle" aria-hidden="true">
    <div class="modal-dialog modal-dialog-scrollable" role="document">
    <div class="modal-content">
    <div class="modal-header">
    <h5 class="modal-title" id="modal_ajaxTitle"> </h5>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
    <div class="modal-body">
    <div class="text-center"></div>
    // the trigger
    < a class="btn icon-btn btn-info pull-right" data-remote="true" data-toggle="modal" data-target="#modal_ajax" href="{{ path('admin_parameters_ajax_add') }}" >

    Dodaj parametr
    < / a>
    {% block javascripts %}
    {{ parent () }}
    {{ encore_entry_script_tags('has_modals') }}
    {% endblock %}

    in file has_modals.js

    import $ from 'jquery';
    // technically, with enableSingleRuntimeChunk(), you can be lazy and
    // not import bootstrap, because it was done in app.js
    import 'bootstrap';

    $(document).ready(function() {
    $('a[data-toggle="modal"]').on('click', function(e) {
    var target_modal = $(e.currentTarget).data('target');
    var remote_content = e.currentTarget.href;
    var modal = $(target_modal);;
    var modalContent = $(target_modal + ' .modal-content');
    modal.on('', function () {
    modal.on('', function () {
    modalContent.html("<div class="modal-header"><h5 class="modal-title" id="modal_ajaxTitle"></h5><button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button></div><div class="modal-body"><div class="text-center"></div></div>");

    return false;


    <div class="modal-header">
    <h5 class="modal-title" id="modal_ajaxTitle">Dodaj parametr</h5>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
    <div class="modal-body">
    {{ form_start(form, {'action': path('admin_parameters_ajax_add')}) }}
    {{ form_end(form) }}

    {{ encore_entry_script_tags('admin_parameters_ajax_add') }}


    //import $ from 'jquery';
    console.log("file admin_parameters_ajax_add loaded");

    I see in console text file admin_parameters_ajax_add loaded but before is deprecated warning :(
    Please help me .

  • 2019-05-24 AlpexDigital

    Sorry, my fault, I forgot to instanciate it:

    let Encore = require('@symfony/webpack-encore');

    Problem solved! Thanks Diego

  • 2019-05-23 Diego Aguiar

    Why it doesn't work? Do you get any errors? What do you see if you console.log it?

  • 2019-05-23 AlpexDigital

    Thanks Vladimir,

    I've tried that before but process.env_NODE_ENV always is "development" :( even if a do a yarn build

  • 2019-05-23 Vladimir Sadicov

    Hey AlpexDigital

    Try to use process.env.NODE_ENV not sure about value inside it, probably better will be to debug it with console.log()


  • 2019-05-23 AlpexDigital

    Thanks for your reply but it doesn't work :(

  • 2019-05-22 Diego Aguiar

    Hey AlpexDigital

    There is a helper method on Encore for doing exactly that Encore.isDev()


  • 2019-05-22 AlpexDigital

    Hi! I'm trying to enable some features only for dev enrironment, this is my postcss.config.js file:

    const purgecss = require('@fullhuman/postcss-purgecss')({
    content: [
    defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []

    let tailwindcss = require('tailwindcss');

    module.exports = {
    plugins: [
    ...process.env.APP_ENV === 'dev'
    ? [purgecss]
    : []

    The APP_ENV seems not to be working, any idea?