Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Importance of Performance

The Importance of Performance

8592f54ed57e8948e890308f6302054a?s=128

Koen Van den Wijngaert

January 09, 2020
Tweet

Transcript

  1. The Importance of Performance By Koen Van den Wijngaert

  2. Performance is Awesome Why I ❤ Performance, and why you

    should, too.
  3. Why Performance Matters

  4. Why Performance Matters ✓ Performance keeps users engaged. ✓ Bad

    performance is costly. ✓ Performance can save lives.
  5. Performance can save lives #663399, aka “rebeccapurple”

  6. Optimizing Performance

  7. Optimizing Performance ✓ Start with performance in mind ✓ Know

    what impacts performance ✓ Collect and monitor regularly Some basic pointers.
  8. Optimizing Performance ✓ Less is more. Send less resources. ✓

    Send resources efficiently. ✓ Limit the size of what you send. It’s not a contest. Golden rules.
  9. Measuring Performance

  10. Performance === UX

  11. The Perception of Delays Delay Perception 0-16ms Animations feel smooth

    and natural. 0-100ms Visual changes in response to input feel instant. 100-300ms Delays are becoming noticeable. 300-1000ms These delays are acceptable for things like long tasks and navigation. >= 1000ms Disconnectedness, harder to keep focus. >= 10000ms Frustration occurs and users are more likely to abandon tasks.
  12. Introducing the RAIL Model

  13. Introducing the RAIL Model

  14. Response The amount of time between a user’s interaction and

    the visual response must be at most 100ms for it to feel instant. Users love snappy navigation. Keep this in mind and your users will love you.
  15. Users spend the majority of their time waiting for sites

    to respond to their input, not waiting for the sites to load. https://developers.google.com/web/fundamentals/performance/rail
  16. Animation ✓ Most screens refresh at least 60 times per

    second. ✓ 60 fps translates to 16ms per frame. ✓ Browser needs ~6ms for rendering. How much time do you have?
  17. Animation ✓ Visual animations (transitions, loading indicators, …) ✓ Scrolling

    ✓ Dragging (Pinching and/or dragging an element) Not just fancy effects.
  18. Animation ✓ Use translations instead of positions (left, right) ✓

    UX friendly easing function: ease-out ✓ Use the right timings: ➢ General rule: 100ms in, 300ms out. * Transition Tips & Tricks * Except for modals, then it’s flipped. ease-out
  19. Use the Magic Window

  20. Idle ✓ Honor the 100ms rule by executing longer tasks

    while the browser is idle. ✓ Don’t block the main thread! ✓ Use blocks of 50ms Time is costly, use it wisely.
  21. Load ✓ First load in 5 seconds or less ✓

    Subsequent loads in 2 seconds or less ✓ Focus on optimizing the Critical Rendering Path Try and load meaningful content as soon as possible.
  22. Optimizing Performance

  23. Optimizing for Performance ✓ Loading Performance ✓ Rendering Performance Two

    main parts
  24. Rendering Performance ✓ How a browser renders your pages ✓

    What impacts rendering performance? ✓ How can we optimize it? A brief introduction
  25. How it Works

  26. Basic Rendering Steps 1. HTML Elements parsed into DOM Tree

    2. CSSOM is generated from CSS selectors 3. DOM + CSSOM = Render Tree
  27. DOM Tree + CSSOM = Render Tree

  28. The Pixel Pipeline

  29. JavaScript Something triggers need for visual changes. ✓ Usually triggered

    by JavaScript ✓ Changes can also be triggered by CSS: ➢ Animations ➢ Transitions
  30. Styling ✓ CSS rules are matched to elements ✓ DOM

    and CSSOM combine into the Render Tree Element style calculations
  31. Layout / Reflow Figuring out what goes where ✓ Based

    on the render tree ✓ Figuring out layouting: ➢ Locations ➢ Dimensions
  32. Paint ✓ Rasterization in tiles ✓ Filling in the pixels

    ✓ Multiple layers Time to start coloring
  33. Composite ✓ Drawing the layers on screen ✓ In the

    correct order Bringing it all together
  34. Pipeline flows

  35. Optimizing Rendering Performance

  36. Optimizing JavaScript-triggered renders ✓ Time your visual changes appropriately ✓

    Avoid big tasks ➢ Use idle time blocks of ~50ms ➢ Offload to Web Workers ✓ DON’T. BLOCK. YOUR. MAIN THREAD.
  37. Use requestAnimationFrame function doAnimation() { // Animation magic. } elem.addEventListener('click',

    (evt) { requestAnimationFrame(doAnimation); });
  38. Also, there’s a special place in hell for people that

    hijack your default scroll behavior. Koen Van den Wijngaert, Ghent, 2020
  39. Optimizing Style Calculations ✓ Simplify your CSS Selectors’ ➢ Scope:

    number of affected elements ➢ Complexity: amount of work required to match an element
  40. Optimizing Style Calculations ✓ KISS: Keep it short, stupid. ✓

    Favor classes over elements and IDs ✓ Processing starts from right to left ✓ Keep the key selector specific
  41. Key Selectors .title { /* styles */ } #head .box:nth-last-child(-n+1)

    .title { /* Your key selector --^ */ }
  42. Optimizing Layout / Reflow ✓ Flexbox and CSS Grid over

    floats and positioning ✓ Layout affects the entire document ✓ Know your enemies ➢ Forced Synchronous Layouts (forced reflow) ➢ Layout Thrashing
  43. Forced Synchronous Layouts function logBoxHeight() { box.classList.add('super-big'); // Logs the

    height of “box” in pixels. console.log(box.offsetHeight); }
  44. Layout Thrashing function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0;

    i < paragraphs.length; i++) { // Set new width of paragraph paragraphs[i].style.width = box.offsetWidth + 'px'; } }
  45. Optimizing the Painting Process ✓ Reduce areas that need repainting

    ✓ Use layer promotion ✓ Simplify by reducing usage of ➢ Blurs, shadows, ... ➢ Overlaps
  46. Layer Promotion .moving-element { will-change: transform; } /* Hackz */

    .moving-element { transform: translateZ(0); }
  47. Compositor Optimization ✓ Compositor-only elements ➢ Transform ➢ Opacity ✓

    Use Layer Promotion ✓ Do some trickery
  48. Know Your Triggers > csstriggers.com

  49. Know Your Triggers > csstriggers.com

  50. Loading Performance

  51. How to Measure?

  52. Loading Performance ✓ Lighthouse ✓ WebPageTest.org ✓ Browser DevTools Measuring

    and Testing: using the right tools
  53. Lighthouse ✓ Browser extension ✓ Online: PageSpeed Insights (may include

    field data) ✓ Comprehensive results ✓ Concrete, actionable advice All-round performance tool https://developers.google.com/speed/pagespeed/insights/
  54. WebPageTest.org ✓ Highly customizable settings (such as browser, location, connection,

    ...) ✓ Modify expert variables ✓ Controlled lab environment ✓ Open Source Online performance testing https://webpagetest.org/
  55. Browser DevTools ✓ Advanced tools for auditing your site ✓

    Important panels: ➢ Network ➢ Performance ➢ Memory In-depth audits in your browser Disclaimer: I use Chrome’s DevTools. Personal preference.
  56. Non-Dev Tools ✓ TestMySite: ➢ Test results compared to industry

    benchmarks ➢ Compare your results with competitors ➢ Basic advice on improving performance ➢ Calculate impact and potential revenue For marketeers, decision takers and beginners
  57. Not all Performance is Equal ✓ Metrics are useless without

    Dimensions ✓ Lab Data !== Field Data Things to keep in mind while testing.
  58. Not all Performance is Equal ✓ Pick user-centered metrics ✓

    Results are not universal; influencing conditions: ➢ Device type (desktop vs. mobile, high-end vs. low-end) ➢ Network type (wifi, 4G, 2G, …) ➢ Connection type (high bandwidth / low latency) Metrics and Dimensions
  59. User Centered Metrics 1. No more blank screen: First Paint

    2. First bit of content: First Contentful Paint 3. Actual useful content: First Meaningful Paint 4. User can start using the page: Time To Interactive It’s all about Perceived Performance and UX.
  60. User Centered Metrics

  61. Not all Performance is Equal ✓ Lab Data is a

    great start for general improvements ✓ Not enough? Try RUM. ➢ Real User Monitoring ➢ Metrics available through JavaScript API Lab Data vs. Field Data
  62. What goes into a network request? 1. DNS: the internet’s

    Yellow Pages 2. Connection negotiation 3. Client sends Request to server 4. Server responds with a Response 5. Repeat if necessary
  63. Real User Monitoring ✓ Detailed loading metrics for resources and

    navigations. ✓ Performance API ➢ Resource and navigation loading metrics ➢ PerformanceObserver For the adventurous amongst us.
  64. Resource and Navigation timings // Get timing data for an

    important image var imgTime = performance .getEntriesByName( "https://.../funny-cat.jpg" ); // Get all timing data at once var allTheTimings = performance.getEntries();
  65. PerformanceObserver // Instantiate the performance observer var perfObserver = new

    PerformanceObserver(function(list, obj) { var entries = list.getEntries(); }); // Run the observer perfObserver.observe({ // Polls for Navigation and Resource Timing entries entryTypes: ["navigation", "resource"] });
  66. Sending back data ✓ If you use the unload event,

    don’t block the thread. ✓ Use navigator.sendBeacon ✓ Best to send raw data and process server-side Things you should know.
  67. Optimizing Loading Performance

  68. Loading Performance Optimization ✓ Optimize content efficiency ✓ Optimize your

    JavaScript ✓ Be lazy. ✓ Order carefully. Loading order is everything The most important ideas in a nutshell.
  69. Not actual advice ;-)

  70. Content Efficiency SO. MUCH. DATA! https://httparchive.org/reports/state-of-the-web, 2010 - 2019 Transfer

    size Total requests We now bundle everything
  71. Content Efficiency Average page bytes by content type. https://httparchive.org/reports/state-of-images

  72. Content Efficiency Be mindful of what you send, and how

    you send it. ✓ Less is more! ✓ Optimize your assets ✓ Optimize your scripts ✓ USE. PROPER. CACHING.
  73. Content Efficiency Less is more. ✓ Do some spring cleaning!

    ➢ Do I need these fancy animation? ➢ Do my users actually view my image slider? ➢ Does this add any value to my content? ➢ What’s the impact on performance? * shouldiuseacarousel.com (spoiler alert: NO)
  74. Content Efficiency Optimizing text-based assets. ✓ Examples: JS, CSS, HTML

    ✓ Minification: content-specific optimizations ✓ Compression: optimizing transfer size
  75. Content Efficiency Smaller transfers with content encoding. ✓ Less bytes

    == faster page loads ✓ Compression algorithms ➢ Drastically reduces file sizes ➢ deflate, gzip and Brotli ✓ Savings range from 60 to 88%
  76. Content Encoding # Client Request Accept-Encoding: gzip, deflate # Server

    Response Content-Encoding: gzip
  77. Content Efficiency Optimizing images. ✓ Avoid them where possible ✓

    Don’t forget about vectors ✓ Use compression (lossless and lossy) ✓ Use the right format
  78. Content Efficiency SVGs are awesome. ✓ Ideal for geometric shapes

    ✓ They scale ridiculously good ✓ Text-based, don’t forget compression ✓ They can be styled using CSS ✓ Re-usable!
  79. Content Efficiency Tips for displaying images. ✓ Don’t forget about

    setting sizes. ✓ Send the right size(s) of images. ➢ Not too big. ➢ Responsive images: srcset and sizes
  80. Responsive images <img src="small.jpg" srcset="small.jpg 500w, medium.jpg 1000w, large.jpg 1500w"

    sizes="50vw" alt="…">
  81. Content Efficiency Image compression. ✓ Lossless compression ✓ Lossy compression

  82. Image Compression Lossless kinsta.com/blog/lossy-compression/

  83. Image Compression Lossy kinsta.com/blog/lossy-compression/

  84. Content Efficiency Use the right image format. ✓ Choose the

    right type: GIF, PNG, JPEG ✓ Consider using modern alternatives: ➢ MP4, WebP, ...
  85. Content Efficiency Font optimizations ✓ Just use the system fonts.

    ✓ Use compression. ✓ Use only what you need. ➢ You probably don’t need Cyrillic, Greek and Vietnamese. ✓ FOIT vs. FOUT
  86. The System Font Stack body { font-family: -apple-system, BlinkMacSystemFont, "Segoe

    UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; }
  87. Flash of Unstyled Text @font-face { /* First display fallback

    font */ font-display: swap; /* Only display if the font is loaded */ font-display: optional; }
  88. Make use of HTTP caching

  89. HTTP Caching ✓ Choose on the right cache policy ✓

    Decide how long things can last ✓ Don’t forget about invalidating A primer on using HTTP caching
  90. HTTP Caching ✓ Cacheability ✓ Expiration ✓ Revalidation and reloading

    The Cache-Control Header
  91. Cacheability Cache-Control: public Cache-Control: private Cache-Control: no-store Cache-Control: no-cache #

    Resource can be cached. # Resource is cacheable *only* for the user. # Prevent caching. # Allow caching, but first check with me.
  92. Cache Expiration # Cached resource considered “fresh” for 120 seconds.

    Cache-Control: max-age=120 # Freshness for public caches. Cache-Control: s-maxage=120 # You don’t need “Expires” anymore. Expires: Thu, 9 Jan 2020 21:00:00 GMT+1
  93. There are only two hard things in Computer Science: cache

    invalidation and naming things. Phil Karlton
  94. Cache Invalidation # Cached resource considered “fresh” for 120 seconds.

    Cache-Control: must-revalidate # Same, but only for public/shared caches. Cache-Control: proxy-revalidate # This resource will *never* change (!) Cache-Control: immutable
  95. Cache Validation: using time # Server Response. Last-Modified: Thu, 9

    Jan 2020 20:00:00 GMT+1 # Subsequent Browser Request. If-Modified-Since: Thu, 9 Jan 2020 20:00:00 GMT+1 # Server will return 304 Not Modified if not modified.
  96. Cache Validation: using tokens # Server Response includes version token

    of resource. ETag: 5485fac7-ae74 # Subsequent browser Request. If-None-Match: 5485fac7-ae74 # Server will return 304 Not Modified if the token matches.
  97. Examples # Static assets may be cached aggressively, for one

    year # Note: “public” is implicit here Cache-Control: public, max-age=31536000 # Store a file for up to one day (eg; animage) Cache-Control: max-age=86400 # Cache, but always revalidate first Cache-Control: no-cache
  98. HTTP Caching Pro Tips Combine to get the best of

    both worlds! ✓ Use consistent URLs ✓ Store and forget: immutable files ➢ Include fingerprint or version number IN the URL. (eg: style.x234dff.css) ➢ Prevents caching inconsistencies ✓ Use validation tokens for variable files
  99. HTTP Caching Pro Tips An example strategy.

  100. HTTP Caching ✓ Cloudflare ✓ nginx ✓ varnish ➢ Highly

    configurable (using VCL - Varnish Configuration Language) ➢ Can evaluate and manipulate requests/responses ➢ Load balancing ➢ Remote purging and invalidation Reverse caching proxies.
  101. Content Efficiency Negotiate and accommodate. ✓ Lots of media queries?

    Group ‘em! ✓ The Accept request header ✓ Client hints are the bomb github.com/SassNinja/postcss-extract-media-query
  102. Grouping media queries <link href="base.css" rel="stylesheet"> <link href="desktop.css" rel="stylesheet" media="screen

    and (min-width: 600px)"> <!-- Yes, print styles are still a thing --> <link href="print.css" rel="stylesheet" media="print">
  103. The Accept Header # I can render fancy image formats

    (Chrome) Accept: image/webp,image/apng,image/*,*/*;q=0.8 # Just the json version, please Accept: application/json
  104. Content Efficiency Accommodate your users with Client Hints ✓ Device

    Hints (Viewport-Width, DPR, Width, Device-Memory, ...) ✓ Network Hints (RTT, Downlink, ECT, Save-Data, ...) ✓ Don’t forget about the Vary header
  105. Client Hints # Response header Accept-CH: Viewport-Width, Downlink Vary: Viewport-Width,

    Downlink # Don’t f* the caches <!-- in html --> <meta http-equiv="Accept-CH" content="Viewport-Width, Downlink">
  106. Optimize your JavaScript ✓ Don’t use huge libraries for a

    small piece of the functionality. ✓ Consider smaller alternatives: ➢ Zepto is a smaller jQuery alternative ➢ Preact is a much smaller alternative to React Reducing the amount of scripts you send.
  107. Optimize your JavaScript The horror stories of unused code

  108. Optimize your JavaScript ✓ Combat unused code ➢ Using Tree

    Shaking ➢ Using Code Splitting ✓ Start experimenting with ES Modules ➢ Loading used JS modules when needed. On bundling your scripts
  109. Be lazy. Images and videos are not uncommon on today’s

    web sites. Don’t make them impact initial loading times. ✓ Lazy load off-screen media ✓ During idle time and/or when they enter the viewport ✓ Please don’t autoplay videos Don’t forget about lazy loading resources.
  110. Be lazy. ✓ Use a library like lazysizes.js ➢ Also

    works with srcset & size. ➢ Customizable: blur, styles, ... ✓ Use fallbacks (noscript and on errors) ✓ Native lazy loading is coming! On lazy loading your media assets.
  111. Lazy loading <!-- lazysizes.js --> <img src="sadface.jpg" class="lazyload" alt="..." width="150"

    height="150" /> <!-- Native! --> <img src="celebration.jpg" loading="lazy" alt="..." width="150" height="150" />
  112. GO OFFLINE

  113. Use Progressive Web Apps ✓ Offline Capabilities using Service Workers

    ✓ Installable on your user’s device ✓ Near-native capabilities Designing offline-first experiences.
  114. Going Offline Because Service Workers are AWESOME.

  115. Service Workers Main advantages. ✓ Full control over the network

    ✓ Endless caching strategies ✓ Providing fallbacks when offline ✓ Background syncing ✓ Higher user engagement with Push Notifications
  116. Optimizing Loading Performance The Golden Rule

  117. Optimize your CRP

  118. Web page loading: an example <html> <head> <link href="style.css" rel="stylesheet">

    </head> <body> <p>Hello world!</p> <div><img src="cat-picture.jpg"></div> <script src="app.js"></script> </body> </html>
  119. The Critical Rendering Path ✓ Progressively load your pages ➢

    Defer render blocking resources ➢ Load critical resources ASAP ✓ Avoid long Critical Request Chains What it takes to start rendering meaningful content.
  120. The Critical Rendering Path ✓ A <script> tag is render-blocking

    if ➢ It’s in the <head> of the document ➢ It does not have a defer attribute ➢ It does not have an async attribute ✓ A <script> tag can also be parser-blocking Render-blocking scripts
  121. The Critical Rendering Path ✓ A <link rel="stylesheet"> tag is

    render-blocking if ➢ It does not have a disabled attribute ➢ It has a matching static media attribute ✓ Note: non render-blocking styles are also downloaded. Render-blocking styles
  122. Critical Rendering Path Avoid long Critical Request Chains

  123. Optimizing the CRP ✓ The number of critical resources ✓

    The critical path length ✓ The number of critical bytes Minimize these things
  124. Optimizing the CRP ✓ Push (or preload) critical resources ✓

    Render the initial content ✓ Pre-cache remaining assets ✓ Lazy load other routes and non-critical assets With the PRPL pattern
  125. Optimizing the CRP ✓ Optimize your CSS: ➢ Inline critical

    CSS, preload the rest ➢ Put all CSS in the head ➢ Avoid @import ✓ Optimize JavaScript usage: ➢ defer and/or async Useful recommendations
  126. Don’t be a dinosaur ✓ Request multiplexing ➢ Sending multiple

    parallel requests over single connection ✓ Header compression ➢ Low overhead. Smaller headers. ✓ HTTP/2 Server Push ➢ Push your critical resources along with your page. Use HTTP/2, it has huge performance benefits.
  127. Resource prioritization by pre-* <!-- Start loading critical styles asap

    --> <link rel="preload" as="style" href="critical.css"> <!-- Start a connection to example.com --> <link rel="preconnect" href="https://example.com"> <!-- Do a DNS lookup for some request in future --> <link rel="dns-prefetch" href="https://example.com"> <!-- Load the next page with lowest priority --> <link rel="prefetch" href="page-2.html">
  128. Performance in WordPress

  129. Performance in WordPress ✓ Backend Performance: TTFB ➢ Running code,

    database communication, external systems, ... ✓ Frontend Performance: Loading ➢ What do I send, how do I send it? How much do I send? ✓ Frontend Performance: Rendering ➢ What happens after loading? How smooth are interactions and navigation? Know what performance is made of.
  130. Performance in WordPress ✓ Have a good hosting environment. ✓

    Minimize your Time to First Byte. ✓ Make sure your configuration is optimized. ✓ Consider the impact of plugins and themes. ✓ Don’t trust theme frameworks and page builders. General recommendations
  131. WordPress Performance Tips ✓ Use the latest software (PHP, database,

    web server, cache, …) ✓ Have their configurations tuned for performance ✓ Run on performant hardware ✓ Be located close to you Good hosting environments should
  132. WordPress Performance Tips ✓ Reduce the number of requests by

    using Content Delivery Networks for static assets. ➢ Cloudflare ➢ Cloudfront ➢ MaxCDN ➢ ... Reduce server load by offloading.
  133. None
  134. Performance in WordPress ✓ Mostly Static ➢ Content does not

    change very often. ➢ Content is not tailored to the user. ✓ Highly Dynamic ➢ Content changes often, or is specific for the current user. How dynamic is your site, really?
  135. Performance in WordPress ✓ Mostly Static ➢ Use the all-mighty

    quick fix: “just cache it”. ✓ Highly Dynamic ➢ Boy, are you in for a treat! Optimizing Backend Performance.
  136. Backend Performance in WP By identifying bottlenecks in the code.

    ✓ By using profilers like xdebug ✓ By using plugins ➢ P3 Plugin Performance Profiler ➢ Debug Bar & Query Monitor Troubleshooting long TTFB.
  137. Backend Performance in WP ✓ Offload slow tasks to background

    ➢ WP-Cron, Cronjobs, external providers ✓ Decrease database query latency ✓ Make use of server side caching ✓ Increase your PHP Workers ➢ More simultaneous requests for high-traffic sites Tips on improving.
  138. Backend Performance in WP ✓ Caching Types ➢ Object caching,

    Page caching, Opcode caching ✓ Caching media/tools ➢ Persistent caching stores (redis, memcache) ➢ APCu ➢ Disk Server Side Caching in WordPress
  139. Performance in WordPress ✓ Use tools like: ➢ Lighthouse ➢

    WebPageTest.org ➢ DevTools Troubleshooting Frontend Performance.
  140. Frontend Performance in WP ✓ Optimize Loading Performance ➢ Be

    mindful of what is being loaded and how. ➢ Optimize the Critical Rendering Path ✓ Make sure network requests ➢ Implement proper caching ➢ Are optimized for fast delivery Improving Frontend Performance.
  141. Frontend Performance in WP ✓ Check your theme and plugin

    settings. ✓ Use plugins to handle things like: ➢ Optimizing network requests ➢ Lazy loading assets ➢ Reducing your CRP ➢ Image delivery and compression Improving frontend performance.
  142. WordPress Optimization Plugins Backend Performance ✓ W3 Total Cache ✓

    WP Super Cache ✓ WP Rocket ✓ Redis Object Cache ✓ Perfmatters Some suggestions, your mileage may vary. Frontend Performance ✓ Asset management: ➢ Autoptimize ➢ Async JavaScript ➢ Hummingbird ✓ Image compression: ➢ Imagify ➢ EWWW (local or Cloud) ➢ WP Smush ➢ ShortPixel
  143. Last but not least...

  144. Don’t fall for the trap! ✓ Law of Diminishing Returns

    ✓ Start with quick wins ✓ There’s more to online success than performance* Know when to stop, don’t overdo it. * Don’t make me say it twice.
  145. “What did we learn today?” - S.O.S. Koen 1. Performance

    Matters 2. Measuring Performance a. It’s all about UX b. The RAIL Model 3. Optimizing Performance a. Loading Performance b. Rendering Performance
  146. Optimizing Loading Performance 1. Measure correctly. Use your tools. 2.

    Send things efficiently. a. Send less b. Reduce transfer size c. Something about caching 3. Optimize your JS. 4. Optimize your CRP. 5. Use HTTP2.
  147. Optimizing Rendering Performance 1. Remember the Pipeline. JS > Style

    > Layout > Paint > Composite 2. Keep it smooth. 3. Something about blocking the main thread.
  148. March 27-29, Antwerp | #WCANT

  149. Have fun Optimizing! @vdwijngaert Koen Van den Wijngaert SiteOptimo.com