's Picture

Nya Expressen.se - ansvarsfullt responsiv

Postad av Joel Abrahamsson

Ett mål med utvecklingen av nya Expressen.se var att bygga en så snabb sajt som möjligt. Ett annat viktigt mål var att få en sammanhållen kodbas för sajten. Tidigare hade vi olika versioner av sajten för olika kanaler och dessa var fördelade över olika applikationer. Närmare bestämt så hade vi:

  • En version med fast bredd anpassad för datorer
  • En till viss del responsiv version för surfplattor
  • En version med variabel bredd för mobiltelefoner

De två första versionerna hanterades av en applikation, Ariel, medan mobilversionen av sajten hanterades av en annan applikation, Haddock.

Det fanns fördelar med denna uppdelning. Vi kunde optimera för varje kanals unika förutsättningar och experimentera med ett subset av den totala trafiken och kodbasen. Men denna uppdelning hade också uppenbara nackdelar. De två kodbaserna divergerade kraftigt redan från dag ett och utvecklare tenderade att bara kunna en av dem väl. Det var också svårt att hålla ihop användarupplevelsen och designen.

Dessutom hände det ganska ofta att vi implementerade ny funktionalitet enbart för en kanal. Detta behöver ju inte vara fel, men ibland ville vi verkligen ha funktionaliteten på samtliga kanaler men efter att vi byggt den för en kanal och mätt effekten var det inte ovanligt att vi gått vidare med annan utveckling och funktionaliteten portades aldrig till övriga kanaler.

Så, när vi nu skulle bygga om sajten från grunden var alla rörande överens om att vi ville ha en enda kodbas för sajten, oavsett kanal. Om det inte rådde några tvivel om detta så var vi betydligt mindre säkra på hur vi skulle göra det praktiskt. Skulle vi bygga en applikation som hanterade tre olika kanaler eller skulle vi bygga en responsiv sajt?

Klart man bygger responsivt! Eller?

Nu för tiden är det närmast märkligt att fråga sig om man ska bygga responsivt eller inte. Att bygga responsivt har blivit i det närmaste standard och samtliga av våra konkurrenter och kollegor hade responsiva sajter när vi ställde oss frågan hur vi ville göra. Men innebär det verkligen att responsivt är det bästa?

Responsiv design har ett antal betydande fördelar:

  • En sammanhållen design över alla kanaler är standard och avsteg därifrån är medvetna
  • En kodbas för alla kanaler är en självklarhet
  • Jämfört med att designa, utveckla och vidareutveckla för separata kanaler är responsivt kostnadseffektivt

Även om ovan fördelar är stora och viktiga så är det intressant att notera att ingen av dem har någon direkt positiv effekt på användarupplevelsen. För slutanvändaren finns det få fördelar med att besöka en responsiv sajt. Däremot finns det (ofta) klara nackdelar i form av extra HTML, CSS och JavaScript som besökarens webbläsare måste hantera. Det kan handla om hela block av HTML och CSS som aldrig ens visas för besökaren, JavaScriptkod och plugins som endast är relevant i en viss kanal eller bara till för att hantera sajtens responsivitet.

Med andra ord: att bygga responsivt riskerar att ge besökare en försämrad prestanda jämfört med en sajt som är optimerad för en given kanal. Det är ju inte så konstigt egentligen, när vi bygger responsivt skjuter vi ju problemet med att hantera skärmbredd och olika layouter i olika kontext till besökarens webbläsare. Naturligtvis går det att göra kreativa lösningar för att minimera prestandapåverkan men det blir sällan helt perfekt. Samtidigt riskerar en del av kostnadseffektiviteten att gå förlorad när vi gör ansträngningar för att prestandaoptimera en responsiv sajt.

Att äta kakan och ändå behålla den

Så, vad skulle vi göra? Vi ville ha de utvecklingsrelaterade fördelarna med att bygga en responsiv sajt utan att det skulle drabba besökarna i form av en långsammare sajt. Vi bestämde oss för att försöka att både äta kakan och spara den. Vi provade att skapa en lösning som vi kom att kalla "responsibly responsive".

När jag som utvecklare kör sajten lokalt på min dator eller när en UX-designer tittar på den på en testmiljö ser vi en responsiv sajt. När vi designar och utvecklar är det en responsiv sajt vi jobbar med.

Men, när du som besökare går till sajten får du bara den HTML, CSS och JavaScript som behövs för din kanal. Alla spår av responsivitet, förutom de som eventuellt behövs för att ge dig en bra upplevelse på den enhet du har, är försvunna.

På detta sätt uppnår vi precis det vi vill; de många utvecklingsmässiga fördelarna med en responsiv sajt samtidigt som våra besökare får en prestandaoptimerad upplevelse anpassad för den typ av enhet de befinner sig på.

Lösning

För att i all väsentlig bemärkelse utveckla en responsiv sajt där alla spår av responsivitet tvättas bort när den möter besökare publikt behövde vi tackla ett antal problem:

  • Kanaldetektering
  • Filtrering av HTML
  • Filtrering av CSS
  • Filtrering av JavaScript

Kanaldetektering

För att avgöra vilken kanal besökaren befinner sig i använder vi, precis som vi länge gjort, vårt CDN (Akamai) som har möjlighet till device detection. När en besökare går till expressen.se skickas anropet till Akamai som känner av vilken typ av enhet besökaren har. Om Akamai inte har cache:at rätt version av sidan sedan tidigare går Akamai ner till våra servrar och lägger till en header med information om vilken typ av enhet besökaren har. Om vi får denna header vet vår applikation att den ska servera en version optimerad för enhetstypen. Om headern inte finns med i anropet vet vi att anropet inte är externt och att vi ska servera en responsiv version av sajten.

Hade vi inte haft tillgång till ett CDN med device detection hade vi naturligtvis kunnat uppnå samma effekt genom att i vår egen applikation titta på vilken user agent besökaren har.

Filtrering av HTML

När man bygger en responsiv sajt kan man göra det HTML- och CSS-mässigt på två olika sätt. I det ena fallet, vilket ofta är att föredra, vet HTML:en inte om att den är responsiv och istället överlåter man helt till CSS:en att hantera hur olika element ska se ut och positioneras vid olika skärmstorlekar och liknande. I det andra fallet, vilket Twitters Bootstrap är ett exempel på, använder man sig av hjälpklasser i HTML:en för att tala om huruvida ett visst element ska vara synligt vid en viss bredd på webbläsaren.

Hade vi byggt en sajt som även publikt skulle varit responsiv hade vi troligtvis valt den första metoden men nu ville vi snarare göra det enkelt att "tvätta" HTML:en från onödiga element och då passade den senare metoden bättre. Detta innebär att när jag tittar på HTML:en som sajten genererar lokalt på min dator, utan kanalfiltrering, så kan den se ut exempelvis så här:

<aside class="site-body__column-3  
  hidden-mobile lp_right">
  ...
</aside>  

När jag istället går till sajten publikt med en dator eller surfplatta ser den istället ut som nedan. Notera att CSS-klassen hidden-mobile är borta:

<aside class="site-body__column-3  
  lp_right">
  ...
</aside>  

Surfar jag istället in med en mobiltelefon är HTML-blocket ovan istället spårlöst försvunnet.

För att åstadkomma detta låter vi vår applikation generera den responsiva HTML:en som vi vill ha med stöd för alla kanaler. Därefter, precis innan applikationen ska svara på det inkommande HTTP-requestet, kickar ett middleware (vi bygger med Node.js) in. Detta tittar på requestet och letar efter en header som innehåller information om vilken kanal som är intressant. Hittar det en sådan header skickas den genererade HTML:en igenom en parser som städar bort alla element som enligt hjälp-klasserna inte ska synas i kanalen. Parsern städar också bort alla hjälpklasser.

Att skicka hela den genererade HTML:en genom en parser kan låta som en dyr operation. Vår parser, som bygger på htmlparser2, är dock mycket enkel och snabb så
det handlar bara om ett fåtal extra millisekunder. Lägg därtill att majoriteten av våra sidvisningar hanteras av vårt CDN så i praktiken är det få request som "drabbas" av dessa extra millisekunder.

Filtrering av CSS

I vår CSS, eller rättare sagt Stylus-kod, skriver vi precis som om vi byggde en responsiv sajt med ett litet undantag; i de fall vi använder oss av media queries för att anpassa för olika enheter gör vi det med hjälp av hjälpfunktioner. Ett fiktivt exempel på hur det kan se ut är så här:

.myElement {
  display: block;
  +mqMinWidth(960px) {
    color: red;
  }
}

CSS:en ovan säger att element med klassen myElement alltid ska ha display: block oavsett kanal. Vidare säger den att om viewportens storlek är 960 pixlar eller bredare ska text i elementet ha röd färg.

När CSS:en byggs skapar vi fyra olika filer; en för varje kanal (mobiler, suftplattor, datorer) samt en responsiv. I den responsiva versionen blir resultatet av ovan Stylus-block:

.my-element {
  display:block
}
@media (min-width:960px) {
  .my-element { color:red }
}

I CSS:en som är byggd för mobiltelefoner blir resultatet istället:

.my-element {
  display:block
}

I CSS:en för surfplattor, där det är möjligt men inte säkert att viewporten är 960 pixlar eller större, blir resultat samma som för den responsiva CSS:en, dvs:

.my-element {
  display:block
}
@media (min-width:960px) {
  .my-element { color:red }
}

För datorer har vi en minsta bredd för sajten som överstiger 960 pixlar. Därför ser CSS:en för dem ut så här:

.myElement {
  display:block;
  color:red
}

Med andra ord får vi efter ett bygge av vår Stylus-kod ut fyra CSS-filer. En av dessa gör sajten responsiv medan de andra tre är tokoptimerade för en specifik typ av enhet. Valet av vilken CSS-fil som ska användas sköts enkelt genom vår funktionalitet för filtrering av HTML. I praktiken ser det ut ungefär så här i HTML:en innan HTML-filtreringen gjort sitt:

<link class="hidden-tablet hidden-desktop hidden-responsive"  
  href="/styles/main.mobile.css">
<link class="hidden-mobile hidden-desktop hidden-responsive"  
  href="/styles/main.tablet.css">
<link class="hidden-mobile hidden-tablet hidden-responsive"  
  href="/styles/main.desktop.css">
<link class="visible-responsive"  
  href="/styles/main.responsive.css">

Efter HTML-filtrering för exempelvis surfplattor blir slutresultatet:

<link href="/styles/main.tablet.css">  

PS: Den observanta som tittar på den faktiska HTML:en från Expressen.se kommer att se att det egentligen snarare ser ut som nedan. Det har att göra med att vi lazy-laddar delar av CSS:en, ett ämne vi lämnar till en annan bloggpost.

<link class="js-lazy-stylesheet" rel="preload"  
  as="style" onload="this.rel='stylesheet'"
  href="/styles/main.desktop__c97c986f2405d297a4be1d7c29552516f.css">

Filtrering av JavaScript

Sist men inte minst behöver vi bygga kanalanpassade JavaScript. Principen är densamma som för CSS. Dvs vi bygger fyra olika filer, en för responsivt läge och en för varje kanal, och låter HTML-filtreringen ta hand om vilken som ska användas.

Till skillnad från CSS:en är det ganska sällan något behöver göras kanalspecifikt i våra JavaScript. I de fall det dock behövs kan vi göra det genom att titta på värdet av ett antal globala variabler som talar om i vilken kanal vi befinner oss.

Ett exempel på där vi kanalfiltrerar JavaScript är anropet till den funktion som visar en knapp för att öppna Expressen-appen. Denna knapp ska bara visas på enheter där appen är tillgänglig, dvs inte på datorer. Så här ser det ut:

if (!CHANNEL_DESKTOP) {  
  openInApp();
}

I det responsiva scenariot innehåller vår JavaScript-bundle funktionalitet som populerar bland annat CHANNEL_DESKTOP utifrån användarens viewport. I händelse av att användaren ändrar webbläsarens storlek uppdateras variabelns värde och events som man kan lyssna på triggas.

För en given kanal processas istället JavaScript-koden, med hjälp av Vanilla shake, och if-satser som kollar vilken kanal besökaren är i tas bort. Det som ligger innanför dem blir antingen kvar eller tas bort beroende på vad som stod i if-satsen och vilken kanals skript som byggs.

Resultatet

När vi först började diskutera idén att bygga en responsiv sajt som prestandaoptimerade på vägen ut till besökarna var de flesta av oss i teamet skeptiska. Skulle detta verkligen fungera i praktiken? Vi såg dock så pass stora fördelar med lösningen om den fungerade att vi bestämde oss för att prova.

Till en början var det en del jobb med att få till de komponenter som jag översiktligt beskrivit ovan, men när de väl var på plats har varje komponent, och lösningen i sin helhet, fungerat fint. Den har också till stor del gett de effekter vi hoppades på vad gäller arbetssätt även om vi ibland har kommit på oss själva att tänka kanalspecifikt. Det kan dock ha att göra med att vi kommer från vår tidigare version av sajten som ju var kanalspecifik.

Prestandamässigt har lösningen levt upp till vad vi hoppades, användarna får bara den HTML, CSS och JavaScript som faktiskt behövs för deras specifika enheter. Nedan är ett exempel på en Lighthouse-mätning mot den responsiva versionen av sajten utan kanalfiltrering:

Givet att Expressens startsida är lååååång är ovan resultat inte direkt dåligt. Men låt oss se på samma mätning mot sajten med kanalfiltrering på, dvs på det sätt som sajten möter våra besökare. Notera speciellt skillnaden i KB under mätpunkten "Unused CSS rules".

PS. Missa inte vår nästa bloggpost, följ oss på Twitter!

Till startsidan