<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fabien Lasserre ☕️</title>
    <description>The latest articles on DEV Community by Fabien Lasserre ☕️ (@fbnlsr).</description>
    <link>https://dev.to/fbnlsr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51329%2F8119613d-588e-4292-a462-a3d8d0092773.jpg</url>
      <title>DEV Community: Fabien Lasserre ☕️</title>
      <link>https://dev.to/fbnlsr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fbnlsr"/>
    <language>en</language>
    <item>
      <title>Containerized Symfony &amp; Vue.js environment with DDEV</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Fri, 02 May 2025 20:37:11 +0000</pubDate>
      <link>https://dev.to/fbnlsr/containerized-symfony-vuejs-environment-with-ddev-30cb</link>
      <guid>https://dev.to/fbnlsr/containerized-symfony-vuejs-environment-with-ddev-30cb</guid>
      <description>&lt;p&gt;For the past year or so, &lt;a href="https://ddev.com/" rel="noopener noreferrer"&gt;DDEV&lt;/a&gt; has been my go-to tool for setting up a development environment. With a simple &lt;code&gt;.yaml&lt;/code&gt; file, this nifty tool is capable of providing a containerized workbench with PHP, a MariaDB/PostreSQL database, and Mailpit. It's perfect for anything from WordPress to the most complex Symfony app. However, out of the box it's not tailored to be used alongside Node. Granted, it does come bundled with Node pre-installed, but if you want to use your PHP backend with a TypeScript frontend, it requires a tiny bit of configuration. This is what this article is about. It is inspired by Andy Blum's blog post over at &lt;a href="https://www.lullabot.com/articles/nodejs-development-ddev" rel="noopener noreferrer"&gt;Lullabot&lt;/a&gt;. My approach is similar but a bit simpler.&lt;/p&gt;




&lt;p&gt;My web stack of choice is Symfony as an API (either RESTful or using GraphQL) and a frontend using Vue.js. I used to rely on &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;NVM&lt;/a&gt; or &lt;a href="https://volta.sh/" rel="noopener noreferrer"&gt;Volta&lt;/a&gt; and make use of my locally installed Node instance, but I wanted to have something completely portable - something where I could just clone a repo, run &lt;code&gt;ddev start&lt;/code&gt; and get to work. To achieve that, we're going to set things up as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up the PHP container and Symfony&lt;/li&gt;
&lt;li&gt;Freeze the Node version and install Vue.js&lt;/li&gt;
&lt;li&gt;Add a reverse proxy to access the frontend&lt;/li&gt;
&lt;li&gt;Enhance the DX thanks to a Makefile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this project, we're going to use a made-up domain - let's say &lt;code&gt;tinydev.ddev.site&lt;/code&gt; - and have the API available at &lt;code&gt;api.tinydev.ddev.site&lt;/code&gt; and the web app at &lt;code&gt;app.tinydev.ddev.site&lt;/code&gt;. I'm going to assume you already have Docker and &lt;a href="https://ddev.com/get-started/" rel="noopener noreferrer"&gt;DDEV installed&lt;/a&gt; and ready to use.&lt;/p&gt;

&lt;p&gt;To keep things tidy, we'll use the following folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/.&lt;span class="n"&gt;ddev&lt;/span&gt;
 - &lt;span class="n"&gt;config&lt;/span&gt;.&lt;span class="n"&gt;yaml&lt;/span&gt;
/&lt;span class="n"&gt;backend&lt;/span&gt;
 - &lt;span class="n"&gt;Symfony&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;...
/&lt;span class="n"&gt;webapp&lt;/span&gt;
 - &lt;span class="n"&gt;Vue&lt;/span&gt;.&lt;span class="n"&gt;js&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/.ddev&lt;/code&gt; folder will contain its configuration file (&lt;code&gt;config.yaml&lt;/code&gt;), with the Symfony API inside &lt;code&gt;/backend&lt;/code&gt; and our frontend inside &lt;code&gt;/webapp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Symfony app
&lt;/h2&gt;

&lt;p&gt;Let's begin by using &lt;code&gt;ddev config&lt;/code&gt; to initialize the project, selecting &lt;code&gt;symfony&lt;/code&gt; as the application type. The docroot for our project will be &lt;code&gt;backend/public&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once configured, open &lt;code&gt;/.ddev/config.yaml&lt;/code&gt; and add the additional host names (&lt;code&gt;api.tinydev&lt;/code&gt; and &lt;code&gt;app.tinydev&lt;/code&gt;). The configuration file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tinydev&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;symfony&lt;/span&gt;
&lt;span class="na"&gt;docroot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend/public&lt;/span&gt;
&lt;span class="na"&gt;php_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8.3'&lt;/span&gt;
&lt;span class="na"&gt;webserver_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-fpm&lt;/span&gt;
&lt;span class="na"&gt;xdebug_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;additional_hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api.tinydev'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;app.tinydev'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;additional_fqdns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.11'&lt;/span&gt;
&lt;span class="na"&gt;use_dns_when_possible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;composer_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2'&lt;/span&gt;
&lt;span class="na"&gt;web_environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;corepack_enable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start DDEV with &lt;code&gt;ddev start&lt;/code&gt;, then enter the web container using &lt;code&gt;ddev ssh&lt;/code&gt;. We'll &lt;a href="https://symfony.com/doc/current/setup.html" rel="noopener noreferrer"&gt;install Symfony&lt;/a&gt; in the &lt;code&gt;backend&lt;/code&gt; directory (at this time of writing the latest version of Symfony is 7.2). We'll have to delete the &lt;code&gt;backend&lt;/code&gt; directory first so that Composer can generate the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; composer create-project symfony/skeleton:&lt;span class="s2"&gt;"7.2.x"&lt;/span&gt; backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, create a simple controller to test everything is working as it should:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// /backend/src/Controller/HomeController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Routing\Attribute\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Route('/')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can go ahead and point your web browser to &lt;code&gt;https://tinydev.ddev.site&lt;/code&gt; and you should be greeted with a "Hello, World!" message. If not, don't hesitate to restart DDEV with &lt;code&gt;ddev restart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, &lt;code&gt;https://app.tinydev.ddev.site&lt;/code&gt; is also pointing to Symfony. Let's take care of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze Node's version and install Vue.js
&lt;/h2&gt;

&lt;p&gt;By default, DDEV comes bundled with Node v22. Let's freeze that so we don't have any bad surprises during our development phase. This can be done very simply inside &lt;code&gt;/.ddev/config.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nodejs_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;22'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead and restart DDEV (&lt;code&gt;ddev restart&lt;/code&gt;) and enter your web container with &lt;code&gt;ddev ssh&lt;/code&gt;. We'll now install Vue.js in the &lt;code&gt;webapp&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; npm create vue@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can choose the flavor you're most familiar with, but the important thing is to specify &lt;code&gt;webapp&lt;/code&gt; as your project's name, as this will be the directory used to install the Vue.js app. Then you can install the app itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;webapp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's point our browser to &lt;code&gt;https://app.tinydev.ddev.site&lt;/code&gt; and get... nothing? That's expected. The app is installed, but it's not accessible yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a reverse proxy to access the frontend
&lt;/h2&gt;

&lt;p&gt;To address this issue, let's add an nginx config file in &lt;code&gt;/.ddev/nginx_full/app.tinydev.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {

    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;tinydev&lt;/span&gt;.&lt;span class="n"&gt;ddev&lt;/span&gt;.&lt;span class="n"&gt;site&lt;/span&gt;;

    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;5173&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_http_version&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Upgrade&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="s1"&gt;'upgrade'&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_cache_bypass&lt;/span&gt; $&lt;span class="n"&gt;http_upgrade&lt;/span&gt;;
    }

    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;;
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;;

    &lt;span class="n"&gt;ssl_certificate&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;certs&lt;/span&gt;/&lt;span class="n"&gt;master&lt;/span&gt;.&lt;span class="n"&gt;crt&lt;/span&gt;;
    &lt;span class="n"&gt;ssl_certificate_key&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;ssl&lt;/span&gt;/&lt;span class="n"&gt;certs&lt;/span&gt;/&lt;span class="n"&gt;master&lt;/span&gt;.&lt;span class="n"&gt;key&lt;/span&gt;;

    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;monitoring&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt;;

    &lt;span class="n"&gt;error_log&lt;/span&gt; /&lt;span class="n"&gt;dev&lt;/span&gt;/&lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;;
    &lt;span class="n"&gt;access_log&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;access&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;;

    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;common&lt;/span&gt;.&lt;span class="n"&gt;d&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;mnt&lt;/span&gt;/&lt;span class="n"&gt;ddev_config&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll also have to edit &lt;code&gt;vite.config.ts&lt;/code&gt; to allow for this new hostname:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /webapp/vite.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;vueDevTools&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite-plugin-vue-devtools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allowedHosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.tinydev.ddev.site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;vue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;vueDevTools&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now restart DDEV (&lt;code&gt;ddev restart&lt;/code&gt;) once more, then hop into the container (&lt;code&gt;ddev ssh&lt;/code&gt;) and run &lt;code&gt;npm run dev&lt;/code&gt; inside the &lt;code&gt;/webapp&lt;/code&gt; directory. Don't forget to run &lt;code&gt;npm install&lt;/code&gt; if you haven't already.&lt;/p&gt;

&lt;p&gt;If you now point your browser to &lt;code&gt;https://app.tinydev.ddev.site&lt;/code&gt;, you should see Vite's default project page.&lt;/p&gt;

&lt;p&gt;We now have a working project using Symfony and Vue.js entirely containerized thanks to DDEV! Yay! 🎉&lt;/p&gt;

&lt;p&gt;You can now enjoy your web app on &lt;code&gt;https://app.tinydev.ddev.site&lt;/code&gt;, and send calls to your API that's residing on &lt;code&gt;https://api.tinydev.ddev.site&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's finally add a bit of magic and enhance our Developer Experience thanks to a simple Makefile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhance the DX thanks to a Makefile
&lt;/h2&gt;

&lt;p&gt;Having to enter the container to start Vite's web server can get tedious. The same goes for simple Symfony tasks like creating a migration or even emptying the cache, we'd have to change directories and tinker with &lt;code&gt;php bin/console&lt;/code&gt; commands. Fortunately, we can use a Makefile as a development toolbox with handy shortcuts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Make sure to use tabs (not spaces) for indentation!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;# /Makefile
&lt;/span&gt;
&lt;span class="nv"&gt;WEBAPP_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/html/webapp
&lt;span class="nv"&gt;API_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/html/backend

&lt;span class="nl"&gt;start&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    ddev start

&lt;span class="nl"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    ddev stop

&lt;span class="nl"&gt;up&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    ddev &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;WEBAPP_FOLDER&lt;span class="p"&gt;)&lt;/span&gt; npm run dev

&lt;span class="nl"&gt;cache-clear&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    ddev &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;API_FOLDER&lt;span class="p"&gt;)&lt;/span&gt; php bin/console cache:clear

&lt;span class="nl"&gt;cc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;cache-clear&lt;/span&gt;

&lt;span class="nl"&gt;migration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    ddev &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;API_FOLDER&lt;span class="p"&gt;)&lt;/span&gt; php bin/console make:migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that you may still want to use &lt;code&gt;ddev ssh&lt;/code&gt; for running Composer, as DDEV executes the &lt;code&gt;ddev composer&lt;/code&gt; command at the project root by default.&lt;/p&gt;

&lt;p&gt;So there you have it! A simple, portable development environment you can use anywhere and share with others.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>symfony</category>
      <category>vue</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to get rid of the Flash Of Unstyled Content</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Wed, 22 Apr 2020 13:58:26 +0000</pubDate>
      <link>https://dev.to/fbnlsr/how-to-get-rid-of-the-flash-of-unstyled-content-5e7</link>
      <guid>https://dev.to/fbnlsr/how-to-get-rid-of-the-flash-of-unstyled-content-5e7</guid>
      <description>&lt;p&gt;This week I spent some time working on &lt;a href="https://www.primative.net" rel="noopener noreferrer"&gt;my website&lt;/a&gt;'s loading performance. I started by switching from &lt;a href="https://kenwheeler.github.io/slick/" rel="noopener noreferrer"&gt;Slick&lt;/a&gt; to &lt;a href="https://glidejs.com/" rel="noopener noreferrer"&gt;Glide.js&lt;/a&gt; in order to remove jQuery as a dependency altogether. This helped me reduce the amount of JavaScript and CSS used by half (!). I then added a language preference cookie. Finally, as a simple way to enhance the user experience, I added a function which would make the switch automatically depending on the browser's language settings.&lt;/p&gt;

&lt;p&gt;Everything was running smoothly, but I couldn't help but notice that my site was suffering from a &lt;a href="https://en.wikipedia.org/wiki/Flash_of_unstyled_content" rel="noopener noreferrer"&gt;Flash Of Unstyled Content&lt;/a&gt;, AKA a "FOUC". It was really noticeable even with the new JavaScript and CSS in place: once a link was clicked, the page would start rendering almost immediately and then the CSS would get applied. This was really annoying as it removes the user from this smooth, almost instant experience I was aiming at. Fortunately, there are a few things we can do to prevent this from happening and get rid of that pesky FOUC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Hide everything!
&lt;/h2&gt;

&lt;p&gt;The first thing we want to do is simply to add a CSS instruction so that our body is hidden from the page until it is ready to be unveiled. This allows the page to be fully loaded before we can finally present it to the user. This might be counter-intuitive since we're aiming at speed, and, well, we're &lt;em&gt;slowing&lt;/em&gt; things there, but it's a sacrifice we're making for the sake of the user's experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"visibility: hidden;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Your awesome website --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could go with &lt;code&gt;opacity&lt;/code&gt; instead, and make use of CSS transitions to add a bit of magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Unveil when everything is ready
&lt;/h2&gt;

&lt;p&gt;We then need to revert that &lt;code&gt;visibility&lt;/code&gt; CSS property once the DOM has been loaded and is ready. For that, I'm using a simple helper function, a bit like jQuery's &lt;code&gt;document.ready()&lt;/code&gt; method. It calls a callback method once the document is in a "complete" or "interactive" state.&lt;/p&gt;

&lt;p&gt;So we simply change the &lt;code&gt;visibility&lt;/code&gt; property of my &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag to &lt;code&gt;visible&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper function&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;domReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;domReady&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Display body when DOM is loaded&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there you go! Our FOUC is gone. With this simple trick, our users get a better experience and don't have a weird mess blinking on their screen before being able to browse our site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Firefox hack
&lt;/h2&gt;

&lt;p&gt;While things should run smoothly on Chrome, you might still see a flash on Firefox. I struggled to find a solution to this problem until I stumbled upon a bug in Firefox that's been reported &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1404468" rel="noopener noreferrer"&gt;3 years ago&lt;/a&gt; and is still active. People are still trying to fix this but lucky for us there's a simple hack we can use as a workaround to this problem. Simply add a dummy piece of JavaScript code right after your &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag and you should be all set!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"visibility: hidden;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty neat, huh? Pretty weird also, I must confess. But hey, it does the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Note: Think of the noscript people
&lt;/h2&gt;

&lt;p&gt;Don't forget that not everybody can or want to execute JavaScript. In that case, this simple line right before our closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag will help make our site seen by everybody.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;noscript&amp;gt;&amp;lt;style&amp;gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&amp;lt;/noscript&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we're all set! Now our site should be displayed correctly, without any FOUC. Yay! 🎉&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update - May 1, 2020&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It has been reported that my code breaks W3C's code validator. That's because officially, the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag cannot be a child of &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To fix this problem, what we can do is remove this &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; tag, and add a &lt;code&gt;no-js&lt;/code&gt; class on the &lt;code&gt;body&lt;/code&gt; element. Then, we simply add this CSS rule in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;.no-js&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally revert this once again right after the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag thanks to JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"visibility: hidden;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"no-js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will not only make this W3C compliant, but since we've added a bit of JavaScript in the body of our document, the dummy JS code we added earlier becomes obsolete! So now, everybody's happy, and we can finally grab a fresh glass of water and enjoy the sun.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flp5oq6bnmakmllganu08.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flp5oq6bnmakmllganu08.jpg" alt="Seal of approval" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Speed up your website in 1 minute with Instant.page</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Thu, 10 Oct 2019 08:32:05 +0000</pubDate>
      <link>https://dev.to/fbnlsr/speed-up-your-website-in-1-minute-with-instant-page-49f1</link>
      <guid>https://dev.to/fbnlsr/speed-up-your-website-in-1-minute-with-instant-page-49f1</guid>
      <description>&lt;p&gt;Behind this clickbait title lies a really nifty little tool. I stumbled upon this while browsing Hacker News a while back. Actually it's been active on this website &lt;a href="https://github.com/fbnlsr/primative.net/commit/d862953f35a2ae0992ed11bd8c294bf8d7658a91" rel="noopener noreferrer"&gt;since february&lt;/a&gt; now but I never took to time talk about it. &lt;strong&gt;Instant.page&lt;/strong&gt; is a tiny Javascript library which is using &lt;em&gt;just-in-time preloading&lt;/em&gt; – it preloads an anchor right before a user clicks on a hyperlink.&lt;/p&gt;

&lt;p&gt;The technique used if fairly simple: &lt;strong&gt;Instant.page&lt;/strong&gt; calculates the time a user spends hovering a link (which is an obvious behavior when you want to click on something) and will start preloadind said link if that time exceeds 65ms. Since &lt;a href="https://www.nngroup.com/articles/response-times-3-important-limits/" rel="noopener noreferrer"&gt;the average human perceives actions taking less than 100ms as instantaneous&lt;/a&gt;, &lt;strong&gt;Instant.page&lt;/strong&gt; tricks the brain, hence allowing your users to get a faster, better experience.&lt;/p&gt;

&lt;p&gt;The installation is fairly simple, just insert the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag containing the link to the script right before the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; of your website (which is where all your scripts should reside anyways), and you're all set! &lt;strong&gt;Instant.page&lt;/strong&gt; will glue itself automatically to each link on the page. The library is small, a mere 1kB. It's free and open source (MIT).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instant.page&lt;/strong&gt; allows you to control what content should be preloaded or not. You might want to prevent it from preloading some anchors, like logout links for instance. To do that, simply add a &lt;code&gt;data-no-instant&lt;/code&gt; attribute to said links and &lt;strong&gt;Instant.page&lt;/strong&gt; will ignore them. You can even specify that you want to allow preloading for external links.&lt;/p&gt;

&lt;p&gt;Simple, light and easy. If you want to try it yourself, check out &lt;strong&gt;Instant.page&lt;/strong&gt; here: &lt;a href="https://instant.page/" rel="noopener noreferrer"&gt;https://instant.page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Forestry - a static website CMS</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Fri, 15 Jun 2018 21:10:19 +0000</pubDate>
      <link>https://dev.to/fbnlsr/forestry---a-static-website-cms-59p7</link>
      <guid>https://dev.to/fbnlsr/forestry---a-static-website-cms-59p7</guid>
      <description>&lt;p&gt;Static websites are getting more and more popular. While some might denigrate this “back to the roots” approach, these systems have numerous advantages. They’re often faster because they’re limited to serving simple web pages, with barely any workload on the serve. The logic is left to be handled by the client, mostly via the usage of APIs, and it’s much easier to put resources in cache and use a CDN during deployment. Besides, these are considered atomic as they allow instantaneous cache invalidation. The website deliverability is therefore 100% assured. Static websites are also often much more secure, as they don’t allow information to be directly sent to the server.&lt;/p&gt;

&lt;p&gt;That being said, the update and deployment of a static website has to be made from a machine equipped with the suitable development environment. A terminal, and tools such as Git, Node, NPM, Ruby and others are often necessary. Indeed, the website has to be compiled beforehand or thanks to an automated process such as GitHub Pages or Netlify.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the need for a static CMS
&lt;/h2&gt;

&lt;p&gt;The need for a static CMS therefore makes sense for multiple reasons. First it allows to overcome the need for a development environment. In the case of a blog, the creation of articles becomes much more fluid, and being able to get away from this technical environment allows for a better workflow for the content creation process. Secondly, it allows non-technical users to manage their website and let them be more free in creating and managing editorial content.&lt;/p&gt;

&lt;p&gt;Today I’m trying out Forestry, a hosted service which makes use of the GitHub API in order to allow a static website to be remotely managed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuja56q11ah0gz6lojo24.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuja56q11ah0gz6lojo24.jpg" alt="Forestry logo" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The installation of the CMS is extremely simple: I just had to create an account on Forestry, link it with my GitHub account and select the repository for the website in question. I then had to specify if the website is generated by Hugo (which is my case) or Jekyll. Quick note: I had to specify the version of Hugo I’m using, and the ones given by Forestry will not necessarily correspond to yours. It wasn’t a problem for me as I’ve configured Forestry not to generate my website, but it’s something to consider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Forestry’s configuration is just as simple. Its system will browse through your repository and generate content types depending on what it finds. All in all, it took me five minutes to have a working back-office and I was able to be productive right away.&lt;/p&gt;

&lt;p&gt;It’s even possible to inject the CMS inside your own site and go through a URL you specify (for instance &lt;a href="http://www.example.com/admin" rel="noopener noreferrer"&gt;www.example.com/admin&lt;/a&gt;), which allows you not to leave your own website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Forestry works by using GitHub, Gitlab or Bitbucket’s API. It’s a fairly simple CMS to use with a clean interface. On the left, a panel listing all the content types allows access for the different categories of the site, and on the right a Wordpress-like panel displays to the Front Matter fields and the main text editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiiprlz21v2h4gz9jhsc7.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiiprlz21v2h4gz9jhsc7.JPG" alt="The admin panel" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, Forestry did an incredible job finding all my blog posts and projects, but was a bit limited when it comes to pages content. Thus, for an unknown reason, the system thought my home page and the “About” page were different, and it put them in two different sections.&lt;/p&gt;

&lt;p&gt;I was surprised to see that even though Forestry uses GitHub’s API, it’s not possible yet to access a content history.&lt;/p&gt;

&lt;p&gt;It’s however possible to rename the file (automatically generated by the CMS) in order to benefit from Hugo’s multilingual support. Thus, by only adding the extension &lt;code&gt;.fr&lt;/code&gt; to an existing slug, I was able to let Hugo automatically link two blog posts and suggest the translated version to the visitors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9joendrcww93hxss0ngc.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9joendrcww93hxss0ngc.JPG" alt="Renaming a file" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, a “project” is exclusively configured thanks to the Front Matter. By changing a couple of lines from the config file, I was able to get rid of the text editor and simply have the configuration fields for a project. For Wordpress users, it’s equivalent to having a page constructed thanks to the Advanced Custom Fields plugin, but generated thanks to a YAML file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2wbew6q536oj74tknav.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2wbew6q536oj74tknav.JPG" alt="Editing a project" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and cons of the CMS
&lt;/h2&gt;

&lt;p&gt;The first obvious con of using this type of CMS is the “technical” aspect that remains very strong even in the presence of a graphical interface. Knowing how to name files is a must in order to allow Hugo to handle them, and some aspects of the interface might scare some users.&lt;/p&gt;

&lt;p&gt;It would have been smart to allow for a hidden, automatic way of naming content files, thanks to a slug system inside the configuration file, and change the language thanks to a select input.&lt;/p&gt;

&lt;p&gt;Adding “users” with restricted permissions is reserved to the “Business” plan which starts at $9 per user per month. In its free version, it’s possible to add “guests” who have almost all rights on the site, which can become pretty problematic.&lt;/p&gt;

&lt;p&gt;For instance, you should note that when it comes to Hugo sites, Forestry enforces the TOML format for the Front Matter and config files. In my case, Forestry detected “menus” for my projects, which are not used anywhere. In trying to test this feature and changing the order of this menu, Forestry decided to rewrite 47 files, including Hugo’s config (writing TOML syntax inside a YAML file), which completely broke my build system. I had to use Git to undo these modifications and come back to a previous working version.&lt;/p&gt;

&lt;p&gt;On another side note, it is to be considered that Forestry lives in Git, and therefore is going to generate a multitude of commits, that won’t certainly respect your commit convention. It’s small point, but it’s something to think about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;After writing all this, one might seem that I did not enjoy my time with Forestry, but it’s far from the truth. While it has some flaws, the team behind it is constantly working on improving it. That being said, and when considering how easy it is to install and use this system, I’d say it’s a really solid choice as a static CMS. It allows to find a certain flexibility when administrating a website while still benefit from the advantages of speed, security and optimizations of a static website generator.&lt;/p&gt;

</description>
      <category>static</category>
      <category>jamstack</category>
      <category>hugo</category>
      <category>forestry</category>
    </item>
    <item>
      <title>10 essential extensions for VS Code</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Fri, 20 Apr 2018 09:24:01 +0000</pubDate>
      <link>https://dev.to/fbnlsr/10-essential-extensions-for-vscode-174i</link>
      <guid>https://dev.to/fbnlsr/10-essential-extensions-for-vscode-174i</guid>
      <description>&lt;p&gt;I've been using &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; as my main code editor for more than two years now. I used to work with Sublime Text, which was an amazing software (especially coming from Notepad++), but the guys at Microsoft are constantly doing an amazing job at making their editor the best out there, and their monthly update shows how dedicated they are to keep at improving it.&lt;/p&gt;

&lt;p&gt;So it's been my editor of choice, but a good editor would be nothing without good extensions. I've compiled a list of my 10 favorites (plus a few more) extensions I could not live without. They make my day to day work much easier and allow me to save so much time in the long run. Here they are (in no special order):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.color-info" rel="noopener noreferrer"&gt;Color Info&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VS Code provides a tiny preview box for colors in CSS files. Color Info allows you to get a much better view of the color your mouse hovers, complete with CMYK or alpha values. It can even act as a color picker, which is really convenient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l0srcpvziuo6fdxnmko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l0srcpvziuo6fdxnmko.png" alt="Color Info" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.emojisense" rel="noopener noreferrer"&gt;:emojisens:&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You know &lt;a href="https://dev.to/blog/lets-talk-about-emojis/"&gt;I love emojis&lt;/a&gt;. This extension allows you to directly insert emojis or type emoji codes in your files. Say you wish to insert a joystick emoji. Just begin to type &lt;code&gt;:joy&lt;/code&gt; for instance and you'll get an auto complete window pop up which will allow to directly insert the 🕹 icon. If you type in &lt;code&gt;::joy&lt;/code&gt;, it'll insert &lt;code&gt;:joystick:&lt;/code&gt; along with a preview of the emoji. It's great!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame" rel="noopener noreferrer"&gt;Git Blame&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As its name states, this simple extension shows the &lt;code&gt;git blame&lt;/code&gt; of the current selected line in the status bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5uo9f54zp2ctcfuqafq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5uo9f54zp2ctcfuqafq.gif" alt="Git Blame" width="869" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one" rel="noopener noreferrer"&gt;Markdown All In One&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I absolutely love Markdown. Actually this whole site makes extensive use of markdown, as it is built with Hugo. This extension helps you write Markdown by adding shortcuts such as &lt;code&gt;Cmd + B&lt;/code&gt; for bold text, &lt;code&gt;Cmd + I&lt;/code&gt; for italics, and so on. So convenient!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" rel="noopener noreferrer"&gt;Settings Sync&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're like me and work on multiple (sometimes virtual) machines, this extension is wonderful! It allows you to sync your settings and extensions thanks to a Github Gist that gets downloaded/uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight" rel="noopener noreferrer"&gt;TODO Highlight&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As the name states, it highlights TODOs, FIXMEs and any keyword you specify. Just write &lt;code&gt;TODO:&lt;/code&gt; somewhere and not only will it highlight it, but it'll also list all the ones you've already written anywhere in your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eaaqivjif6i3rlj9omz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eaaqivjif6i3rlj9omz.png" alt="Todo Highlight" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=octref.vetur" rel="noopener noreferrer"&gt;Vetur&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This extension is a swiss knife for Vue. From autocompletion to snippets, it's a must-have for any frontend developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=ban.spellright" rel="noopener noreferrer"&gt;Spell Right&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A multilingual, offline and "lightweight" spellchecker. Spell Right uses your built-in dictionaries to check for errors, and can check for errors in any (and even multiple) language anywhere within your project. Watch out for big files though, as it can sometimes take some time to operate. I usually have it toggled off by default. It's easy to ask it for a spell check just by clicking on the eye icon in your status bar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig" rel="noopener noreferrer"&gt;EditorConfig for VS Code&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Strangely enough, VS Code does not support &lt;a href="http://editorconfig.org/" rel="noopener noreferrer"&gt;EditorConfig&lt;/a&gt; by default. Just install this extension and it'll instantly start listening to any &lt;code&gt;.editorconfig&lt;/code&gt; file it encounters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks" rel="noopener noreferrer"&gt;Bookmarks&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now this one I use it constantly! Bookmarks is an extension that puts little blue bookmarks in your file gutter. It's extremely handy when you need to switch between positions inside a file, or if you need a quick reminder anywhere in your project. I've set up mine with &lt;code&gt;shift + cmd + =&lt;/code&gt; (toggle bookmark) and &lt;code&gt;shift + cmd + -&lt;/code&gt; (next bookmark) and thanks to this I can jump around files without having to leave my keyboard. The extension also adds a small panel right below your file browser which lists all active bookmarks in the current project. A must-have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24tf95zy7o7jzva1awkn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24tf95zy7o7jzva1awkn.png" alt="Bookmarks" width="664" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Honorable mentions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=JerryHong.autofilename" rel="noopener noreferrer"&gt;AutoFileName&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This extension is pretty straightforward. Just type in the beginning of a file/directory and it'll autocomplete its name for you. Really handy when you need to point to a file inside &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=JakeWilson.vscode-cdnjs" rel="noopener noreferrer"&gt;cdnjs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most of the time I inject my dependencies in my Javascript files thanks to Webpack. But whenever I need to mockup something quickly, this extension has my back. Using the command palette, you'll be able to insert URLs or script/style tags of all the libraries cdnjs handles. Quite handy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9thdddnintvun1b0tyo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9thdddnintvun1b0tyo.gif" alt="cdnjs" width="776" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Got one to share?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So here are a few extensions I use every day. If you've got one I should check out, hit the comment section or &lt;a href="https://twitter.com/fbnlsr" rel="noopener noreferrer"&gt;send me a tweet&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vscode</category>
      <category>plugins</category>
    </item>
    <item>
      <title>Let's talk about emojis</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Fri, 12 Jan 2018 10:44:44 +0000</pubDate>
      <link>https://dev.to/fbnlsr/lets-talk-about-emojis-2788</link>
      <guid>https://dev.to/fbnlsr/lets-talk-about-emojis-2788</guid>
      <description>&lt;p&gt;Emojis are everywhere. From Twitter to Facebook Chat, they've grown to become Oxford's 2015 &lt;a href="http://time.com/4114886/oxford-word-of-the-year-2015-emoji/" rel="noopener noreferrer"&gt;Word of the Year&lt;/a&gt; and even featured in a &lt;a href="https://www.rottentomatoes.com/m/the_emoji_movie" rel="noopener noreferrer"&gt;horrendous movie&lt;/a&gt;. But what about outside SMS and instant messaging? What about using emojis inside code comments or even git commit messages? Let's find out how we can make the best use of these funny little pictures.&lt;/p&gt;

&lt;p&gt;Contrary to what people may think, emojis have been around for quite some time. The first emoji dates back to 1999, and was created by Shigetaka Kurita, a Japanese telecommunication planner for NTT Docomo. At first used solely in Japan, it took those little pictures ten years for some of them to be added to the Unicode character space. Thus, in October 2010 the Unicode Standard 6.0 got released, and with it 722 emojis. They do not live in their own dedicated blocks though and are spread around the Unicode tables. It took years for multiple engineers working at Google and Apple to convince the Unicode Technical Committee to add them. Now emojis are a part of everybody's life.&lt;/p&gt;

&lt;p&gt;There are even some quirks and fun little facts about these tiny pictures. For example: emojis can vary from one platform to another. Because of that, the calendar emoji is represented always showing July 17 on Apple products (that date representing the announcement of iCal back in 2002). This led people to "wrongfully" declare July 17 World Emoji Day.&lt;/p&gt;

&lt;p&gt;Emojis are also represented differently across platforms, and can be interpreted slighly differently. Take for instance the &lt;code&gt;astonished face&lt;/code&gt; emoji. The first one is Apple's interpretation, the second one is Samsung's.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdemt03kk5ngfnhbkltvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdemt03kk5ngfnhbkltvj.png" alt="emoji1" width="212" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apple's take on this feeling feels a bit more tamed than Samsung's, don't you think?&lt;/p&gt;

&lt;p&gt;Other times, it can be the contrary. In this example, Samsung's interpretation of the &lt;code&gt;pouting face&lt;/code&gt; feels less "angry" than Twitter's.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mnzpm6ta1pr6betk5yq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mnzpm6ta1pr6betk5yq.png" alt="emoji2" width="212" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But enough with the history, let's get down to coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emojis in git commit messages
&lt;/h2&gt;

&lt;p&gt;Github has popularized emoji support inside their ecosystem in a &lt;a href="https://github.com/blog/1289-emoji-autocomplete" rel="noopener noreferrer"&gt;blog post from 2012&lt;/a&gt; thanks to their now famous "&lt;code&gt;:&lt;/code&gt;" shortcut. So now, say you want to use the &lt;code&gt;fox face&lt;/code&gt; emoji 🦊 somewhere in Github (a commit message, an issue or a gist), you can simply use &lt;code&gt;:fox_face:&lt;/code&gt; instead and it will automatically be interpreted by Github.&lt;/p&gt;

&lt;p&gt;Using shortcuts is an elegant solution to circumvent emojis not being interpreted. You don't take the risk of breaking something and even if they're not (or badly) rendered, the messages are still readable.&lt;/p&gt;

&lt;p&gt;Emojis can also add a lot of clarity to commit messages. Compare these two sequences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- Fix editing user not being saved to the database
- Cleanup code
- Add the ability to edit a user
- Fix bad function callback on API request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- 🐛 Fix editing user not being saved to the database
- 📝 Cleanup code
- ✨ Add the ability to edit a user
- 🐛 Fix bad function callback on API request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can immediately see where bugs were fixed and where new features were added.&lt;/p&gt;

&lt;p&gt;On a platform that doesn't support emojis, this would be read as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- :bug: Fix editing user not being saved to the database
- :memo: Cleanup code
- :sparkles: Add the ability to edit a user
- :bug: Fix bad function callback on API request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Definitely not as fun, but still perfectly readable.&lt;/p&gt;

&lt;p&gt;The tech industry as a whole appropriated these shortcuts and &lt;a href="https://www.webpagefx.com/tools/emoji-cheat-sheet/" rel="noopener noreferrer"&gt;went far beyond&lt;/a&gt; simple emojis. Sure it's nice to use 🐛 to talk about fixing a bug, but try using &lt;code&gt;:trollface:&lt;/code&gt; in Slack or Redmine. Boom, you're the new cool kid on the block. Don't use it too often though, you don't want to be &lt;em&gt;that guy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My advice:&lt;/strong&gt; Don't hesitate to use emojis in git commits, but do prefer short-codes. I would also suggest not to go overboard with it and stick to a list of a few ones to denote the major actions (bugfix, new feature, styling, code cleanup, etc..).&lt;/p&gt;

&lt;p&gt;If you're not sure were to start or want to suggest a guideline for your team, I highly recommend Carlos Cuesta's &lt;a href="https://gitmoji.carloscuesta.me/" rel="noopener noreferrer"&gt;Gitmoji&lt;/a&gt;. It even comes with a nifty CLI (simply called &lt;a href="https://github.com/carloscuesta/gitmoji-cli" rel="noopener noreferrer"&gt;&lt;code&gt;gitmoji-cli&lt;/code&gt;&lt;/a&gt;) which will help you write your commit messages through an interactive interface. Gitmoji is even used in Atom's &lt;a href="https://github.com/atom/atom/blob/master/CONTRIBUTING.md#git-commit-messages" rel="noopener noreferrer"&gt;contribution guideline&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emojis in/as code
&lt;/h2&gt;

&lt;p&gt;Technically, you &lt;em&gt;could&lt;/em&gt; use emojis in computer code, but you should be very careful when doing so. Emojis are interpreted as strings in Javascript, but their length can vary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🐼&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;         &lt;span class="c1"&gt;// returns 2&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🇨🇦&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;         &lt;span class="c1"&gt;// returns 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget emojis can be connected (kind of the same way Fira Code gets you those sexy sexy ligatures). That's how you can now get skin color modifiers (called &lt;code&gt;EMOJI MODIFIER FITZPATRICK TYPE-1&lt;/code&gt;, &lt;code&gt;-2&lt;/code&gt;, &lt;code&gt;-3&lt;/code&gt;, &lt;code&gt;-4&lt;/code&gt;, &lt;code&gt;-5&lt;/code&gt;, and &lt;code&gt;-6&lt;/code&gt;. I'm not kidding). Or even better, if you combine the following emojis: 👨, 👩, and 👧, you get a whole family 👨‍👩‍👧! Let's run that through Javascript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;👨‍👩‍👧&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;         &lt;span class="c1"&gt;// returns 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why 5? Because not only do you get the length of each emoji that symbol is made of, but it also uses two &lt;code&gt;ZWJ&lt;/code&gt; (Zero Width  Joiner) characters as "glue". You can even see it in action: copy/paste that emoji inside VS Code for instance, and it'll take you five "arrow key" strokes to go through it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My advice:&lt;/strong&gt; Do not to use emoji in code logic. Plain and simple. But you can still use them in your views. Web browsers have amazing emoji display capabilities, and know how to fallback to a font that &lt;em&gt;will&lt;/em&gt; display your "thump up" icon. But watch out and be careful when using an emoji short-code interpreter in those views, especially if you happen to display code blocks on your website. It could trick you, interpreting &lt;code&gt;h:m:s&lt;/code&gt; as &lt;code&gt;hⓂ️️s&lt;/code&gt;, thus making the code block useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emojis in code comments
&lt;/h2&gt;

&lt;p&gt;So what about code comments? Emojis everywhere! As far as I know, you are not susceptible to break anything because of emojis in comments. Modern code editors (Atom, VS Code, Sublime, Intellij...) have amazing emoji support. They even can be pretty useful to make something stand out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * WARNING: Do NOT change this file.
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * 🛑 WARNING: Do NOT change this file.
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Emojis are a double edged sword. They allow us to express complicated feelings in a quick and fun way. They are the extension of the emoticons we used profusely back in the IRC glory days. They can be used as decorators, adding feeling to an otherwise plain sentence. They also can be used as markers to make something stand out, and even as a complete communication tool when used on their own.&lt;/p&gt;

&lt;p&gt;However, since they're not designed and interpreted uniformally across platforms, they can be the source of misunderstandings. Communication relies on the stability of its mean of propagation. If a symbol is changed between the sender and the receiver, the message is not the same. As characters, they also need to be put in context. That's why some of them had to be changed. For instance the &lt;code&gt;:gun:&lt;/code&gt; emoji 🔫 which used to be represented by a real gun, is now a water pistol.&lt;/p&gt;

&lt;p&gt;When it comes to code though, I'm all in favor of using emojis. Not in the code itself, as I've stated, but rather in comments and commit messages. They embelish the message they're attached to, for they are generally mainly used as pointers. And with the help of short-codes, you can use them without the fear of breaking something.&lt;/p&gt;

&lt;p&gt;If you want to know more about emojis, you should check out &lt;a href="https://meowni.ca/" rel="noopener noreferrer"&gt;Monica Dinculescu&lt;/a&gt;'s work, and especially her talks.&lt;/p&gt;

&lt;p&gt;I also recommend Angela Guzman's post on &lt;a href="https://medium.com/@agzmn/the-making-of-apples-emoji-how-designing-these-tiny-icons-changed-my-life-16317250a9ee" rel="noopener noreferrer"&gt;the making of Apple's emoji&lt;/a&gt;. Angela writes how she and her mentor Raymond designed over 500 emojis during her internship back in 2008. This changed her life, and her work is now in the hands of millions of people.&lt;/p&gt;

&lt;p&gt;So go ahead and emoji away, you'll improve readability and break away from the monotony of a dull screen filled with code. 😄&lt;/p&gt;

</description>
      <category>emoji</category>
      <category>code</category>
      <category>thoughts</category>
    </item>
    <item>
      <title>My backup strategy</title>
      <dc:creator>Fabien Lasserre ☕️</dc:creator>
      <pubDate>Wed, 03 Jan 2018 17:27:43 +0000</pubDate>
      <link>https://dev.to/fbnlsr/my-backup-strategy-4gi7</link>
      <guid>https://dev.to/fbnlsr/my-backup-strategy-4gi7</guid>
      <description>&lt;p&gt;The protection of digital assets is a multi-million dollar industry. Whether we're talking about military, financial or scientific data, each industry has to be prepared in the event of a loss, and plan for security. They often roll out extreme measures, going as far as having their own (and doubled) dedicated electrical power lines. But what about safeguarding your friend's latest BBQ party pictures? Or your little one's first steps video? Here's how I've learned my lesson from a tragic system failure and what my current setup looks like now.&lt;/p&gt;

&lt;h2&gt;
  
  
  A catastrophic failure
&lt;/h2&gt;

&lt;p&gt;Back in 2008, I made myself a custom NAS (&lt;strong&gt;Network Attached Storage&lt;/strong&gt;) using some old computer parts, a bunch of 500 GB hard drives and a copy of &lt;strong&gt;FreeNAS&lt;/strong&gt;. The OS ran off a nifty 512 MB IDE flash based drive, and the data array was configured to use &lt;strong&gt;RAID5&lt;/strong&gt;. That meant that if a drive was to be damaged, I could always put a new drive in the array and the data would rebuild itself. Note the conditional tense. That's because it worked flawlessly until we moved in 2010. And we stored the NAS in a box next to a speaker with a huge magnet for a month. And two drives failed.&lt;/p&gt;

&lt;p&gt;I spent weeks trying to figure out a solution on how to rebuild the data I had lost. But after some time I had to face the reality of things. It was in vain. Years of family photos and videos, an entire MP3 collection, all my video games... It was all lost, forever. My girlfriend was in tears and my "geek pride" after spending all that time planning and building this whole system took a serious hit. I was using hot data as a storage, and at the time I had no backup strategy. Of course I had recovery options, hence the RAID5, but I was not prepared for such a catastrophic failure. And when it comes to computer security, you &lt;em&gt;have&lt;/em&gt; to prepare for the worse.&lt;/p&gt;

&lt;p&gt;Years later, I learned my lesson. So here is how I handle my digital life now.&lt;/p&gt;

&lt;h2&gt;
  
  
  My current setup
&lt;/h2&gt;

&lt;p&gt;My current setup is mainly built around two things: a new NAS, that I bought and not built, and a backup software that automates how data is handled.&lt;/p&gt;

&lt;p&gt;The NAS I'm using now is a &lt;strong&gt;Synology DiskStation DS214se&lt;/strong&gt;. It's a very simple machine, running on a 800 MHz dual core CPU, with 256 MB of RAM and two hard drive bays. I put in there &lt;strong&gt;two 2 TB Western Digital Green&lt;/strong&gt; hard drives, and configured a single array to be run in &lt;strong&gt;RAID1&lt;/strong&gt;: everything that's on one drive gets mirrored to the second one. That means that I lose half of the hypothetical storage space but if one drive fails, I can change it and the data will automatically rebuild itself.&lt;/p&gt;

&lt;p&gt;The NAS sits on top of an &lt;strong&gt;APC Uninterrupted Power Supply&lt;/strong&gt;. If power is lost in my apartment, the NAS keeps running and I can manually (and safely) shut it down either by using its physical power button (which sends a power off command) or even my phone (my router is also plugged in to the UPS, so even without power I still have internet and network access for a few minutes).&lt;/p&gt;

&lt;p&gt;My main backup strategy is handled by an amazing software called &lt;strong&gt;SyncBack Free&lt;/strong&gt;. This software allows me to set up various backup scenarios, called profiles. The main profile is a &lt;strong&gt;physical backup&lt;/strong&gt; to an external hard drive. When I bought the Synology NAS, I got a third 2 TB drive that is now used as a backup. This is my first failsafe. This is what lacked in my previous setup. Once the backup task is done, that drive is stored &lt;strong&gt;offline&lt;/strong&gt; and &lt;strong&gt;off-site&lt;/strong&gt;, so it doesn't have to suffer from electrical malfunctions. And even in the event of a fire or flood at my place, my data is safe.&lt;/p&gt;

&lt;p&gt;SyncBack then runs two more jobs. Amongst all the data I've lost with that old setup, the loss of family photos were the hardest to cope with. One can always replace music or movies they used to love, as there's a never-ending stream of entertainment to consume. But memories do fade away, and are impossible to retrieve. So I've decided to add another redundancy layer to my backup strategy when it comes to photos and store them online, in my &lt;strong&gt;Google Drive&lt;/strong&gt;. SyncBack compares the content of the NAS folder and my Google Drive, and updates the later with the former before performing a &lt;strong&gt;Cyclic Redundancy Check&lt;/strong&gt; of each file to see if they are the same on both sides.&lt;/p&gt;

&lt;p&gt;I should note that I could use two different apps on the NAS and have it handle these two backups automatically: &lt;strong&gt;USB Copy&lt;/strong&gt; and &lt;strong&gt;Hyper Backup&lt;/strong&gt;. After trying out both apps in different scenarios, I've decided not to use them as they either store data in a proprietary format (Hyper Backup) or add a bunch of &lt;code&gt;._&lt;/code&gt; prepended metadata files to my existing directories (USB Copy). I like the fact that if I ever need to retrieve my files outside of Synology's ecosystem, I still can use a good old &lt;code&gt;cp&lt;/code&gt; command to get my files back.&lt;/p&gt;

&lt;h2&gt;
  
  
  But wait, there's more!
&lt;/h2&gt;

&lt;p&gt;So my data is stored on a RAID1 array, and on an offline hard drive. And the photos are backed up online on my Google Drive. I could have stopped there but I thought that it was not enough. Thanks to my &lt;strong&gt;Amazon Prime&lt;/strong&gt; subscription, I can upload an infinity number of photos on their &lt;strong&gt;Amazon Drive&lt;/strong&gt; cloud service and it won't affect my otherwise limited quota. So hey, let's take this opportunity! Another SyncBack profile backs up the content of my Photos directory to Amazon's servers. I like the fact that my data is stored on two different storage providers. Google and Amazon each have their own infrastructure, so in the event of a failure of astronomical proportions at either one of these places, I &lt;em&gt;may&lt;/em&gt; still be safe.&lt;/p&gt;

&lt;p&gt;But why stop there? My photos are stored on four different locations now (The NAS, the external hard drive, Google Drive and Amazon Drive). But what about the rest? My music, my documents, my family videos? Well of course they're on the NAS and the external hard drive, but I figured I needed another failsafe. Because so far my backup strategy relies on what could constitute a &lt;strong&gt;single point of failure&lt;/strong&gt;: SyncBack. If the software behaves badly or one of my backup profile is not properly configured, I may end up with nothing but a bad save on various locations. I also don't have access to the external hard drive that easily, so if I need to do a backup at any given time, I need to prepare the operation at least a day in advance.&lt;/p&gt;

&lt;p&gt;That's why I took a subscription to &lt;a href="https://c2.synology.com/en-us" rel="noopener noreferrer"&gt;&lt;strong&gt;Synology C2&lt;/strong&gt;&lt;/a&gt;. It is a fully integrated service that runs natively on &lt;strong&gt;DSM&lt;/strong&gt; (&lt;em&gt;DiskStation Manager&lt;/em&gt;: Synology's own operating system) and allows me to back up the whole NAS (minus my movies and TV shows, these are not important) to Synology's servers. It uses AES-256 to locally encrypt the data before sending it on the network. I've set it up so it does an automated backup every first day of the week, and then do an integrity check two days later.&lt;/p&gt;

&lt;p&gt;I also considered Online's &lt;a href="https://www.online.net/en/c14" rel="noopener noreferrer"&gt;C14&lt;/a&gt;, as they're really cheap and you can send files over (S)FTP but unfortunately they do not support Synology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Room for improvement
&lt;/h2&gt;

&lt;p&gt;So this is what my current setup looks like now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10g38er1doslmfx11p93.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10g38er1doslmfx11p93.jpg" alt="My current setup" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each file is physically stored on up to 6 different location, with various levels of failsafe measures.&lt;/p&gt;

&lt;p&gt;Is this setup perfect? Of course not. First and foremost, it lacks automation. I still have to start each backup task (except for the C2 one) manually, and it is prone to error. I'm working with live data so the array is constantly changing, but this is a backup, not a long term cold storage. And the external hard drive I'm using has to be transported and manipulated, so that's another weak point in the system.&lt;/p&gt;

&lt;p&gt;One thing I'll probably change soon is the model of the hard drives I'm using. WD Green are "fine" but they are not made for being used in a NAS. So I think I'll switch them for either WD Red or Seagate Ironwolf line, and probably take the opportunity to do a slight storage upgrade to 3 or 4 TB.&lt;/p&gt;

&lt;p&gt;All in all, the main problem with backup strategies is that they're never perfect. Just look at &lt;a href="https://techcrunch.com/2017/02/01/gitlab-suffers-major-backup-failure-after-data-deletion-incident/" rel="noopener noreferrer"&gt;what happened at GitLab&lt;/a&gt; a few months ago, or even the catastrophic failure that &lt;a href="https://www.theregister.co.uk/2017/07/13/watercooling_leak_killed_vnx_array/" rel="noopener noreferrer"&gt;brought OVH to its knees&lt;/a&gt; for hours.&lt;/p&gt;

&lt;p&gt;One cannot be fully prepared against data loss. Still, I can say that I feel &lt;em&gt;somewhat confident&lt;/em&gt; with this strategy, and I've tried to think about every scenario (even solar flares, but they're a whole another animal). We'll see how and where my data sits in a few years.&lt;/p&gt;

</description>
      <category>backup</category>
      <category>hdd</category>
      <category>nas</category>
      <category>synology</category>
    </item>
  </channel>
</rss>
