7
11 Comments

20ms landing page in 7 steps

While it's not as good as solar.lowtechmagazine.com which runs on a solar powered potato, 20ms is good enough to prevent bounce rate caused by users getting bored and leaving. See for yourself: django.doctor

If 20% of users are bouncing due to getting bored of waiting then that 20% potential conversion lost right there. It takes a lot of effort to optimize, but 20% increase in conversions sounds good to me.

I will explain what I did to make my landing page quick.

Static website on S3

The website is written with React and hosted on an S3 bucket. The advantage here is there is no webserver that I have to run that can be DDOSed. Imagine the pain of the landing page going down or taking longer to load because it's too popular. That "cannot" happen to S3.

Server side rendered

If we just uploaded the built React website to S3 then the user's browser would do this:

"oh I need to download download index.html"
"ah index.html wants me to also download index.js. I'll do another HTTP request"
"ah ok the index.js ContentType headers say this is javascript. Let's execute it"
"Cool! it's a Single Page Application. I'll render it"

While step 2, 3, and 4 are happening what does the user see? A blank white screen. On slower internet or mobile this is more noticable. On fast internet there is still a white flash. If some temporary networking error happens on step 2 then the white screen will be white forever (which is why your site should be functional without js)

Surely there has to be a better way! Yep: Server Side Rendering. When I deploy, instead of just uploading index.html to the s3 bucket, I run a script that effectively crawls every page on my site and then saves the html that the React app rendered. So the user's browser now does this:

"oh I need to download download index.html"
"Oh it's full of HTML. I'll render it."
... and then it also downloads index.js to make the site dynamic and React-y, but who cares the potential customers can see your value proposition immediately

No white flash. Better user experience. Immediate value shown.

I use react-snap for this.

Gzip

Why make the browser download a 90kb index.html file when it could be 15kb? These things matter for mobile. So before uploading the server side rendered html file I gzip relevant files:

# compress all relevant files using gzip
find build -type f ( -name *.js -o -name *.css -o -name *.html -o -name *.svg -o -name *.map ) -exec gzip {} ; -exec mv {}.gz {} ;

# compressed files, including the index.html file
aws s3 cp build s3://django.doctor --exclude *.png  --acl public-read --region eu-west-2 --content-encoding gzip --recursive;

# uncompressed files
aws s3 cp build s3://django.doctor --exclude '*' --include *.png --acl public-read --region eu-west-2 --recursive;

Cloudfront

S3 buckets are region specific, so if I upload to London then potential customers in London get the advantage of speed while it's slow for Californians. I configure AWS cloudfront to serve the S3 bucket, so no matter where the page is requested in the world the browser will be served from the best bucket. This also makes it more fault tolerance in case of region specific outage.

Cache headers and file revving

When the potential customer visits for the second time why make the browser re-download index.js, stylesheet and images if it's already downloaded them? We could configure cloudfront to cache the files forever. However, what happens when index.js changes? Without configuring cloudfront for this then the browser will still use the cached version. This can be solved by using file revving during the build process, which react-create-app's build step does by default.

So I configured cloudfront to cache every file EXCEPT index.html forever. This ensures the the browser caches the larger expensive files but also ensures the browser will always get the newest version of the website.

Lazy load pages

OK now the browser has downloaded the single page React application. Great. But the user just wanted to see the landing page. Why am I making them wait while the code for /about, /terms, /privacy etc also downloads? React has a feature we can use React.Suspense. It looks like this when using react router:

import LandingPage from './LandingPage'

const Terms = React.lazy(() => import('./Terms'))

   <Router>
        <Route  exact path="/" component={LandingPage} />} />
        <React.Suspense fallback={<Loading /> } >
          <Switch>
            <Route exact path="/terms" component={Terms} />
          </Switch>
        </React.Suspense >
   </Router>

So react-create-app will automatically bundle the app into multiple files - one containing the non-lazy stuff and one containing the lazy stuff. When lazy files will be downloaded by the browser in the background and the content will be there when they click the link.

Lazy load sections

OK now why are users waiting for the "below the fold" content and images to load when their viewport is above the fold? They've note even seen the hero yet the browser is downloading an image gallery half way down the screen? To prevent this we use react-lazy-load, so my landing page react looks like

    <>
      <Hero />
      <Clients />
      <PullRequestCheck />
      <Features />
      <Testimonial />
      <LazyLoad height="1600px">
        <ExampleCompanionWebsite />
      </LazyLoad>
      <Price />
    </>

The expensive components with nice images get loaded only when the user scrolls close to it.

Lossless compression

And finally. Why is the browser spending time downloading colours and minutiae in an image that the human eye can't really see? We can reduce the download size of images by maybe 75% by using lossless or lossy compression.

Conclusion

Putting all these together, the landing page loads blazingly fast. So no one is bouncing due to page load issues. Next challenge is now the content has loaded how can I best demonstrate the value I'm offering with my product,
django.doctor?

  1. 1

    WOW!
    The website was so quick to load that I freaked out.
    Nice Job man 💪

    It was quite an experience to browse.

    Is there any nodejs.doctor or express.doctor?

    1. 1

      haha perfect :)

      good idea. eventually there might be

  2. 1

    Nice work.

    I love Django Doctor! Tried out your challenges the other day and I'm amazed. I'm plan to nudge my client into using once we get more bandwidth for craftsmanship.

    Do you support DRF?

    When will you add more challenges 😁?

    1. 1

      Thanks! Glad you like it.

      I'll add another challenge next week just for you :)

      regarding DRF. I've been thinking of expanding to include other django libraries but have not got a solid list of DRF anti-patterns to check for.

  3. 1

    Hi! That's cool, but not too difficult to achieve if you front your site with a CDN, HTML included :)

    I have considered this approach for https://www.dynablogger.com/ because my user sites are mostly reads, so I could in theory front that content with a CDN. However it would be complicated and not scalable as the number of users and sites grows, because unless I am missing something I would need one bucket per site and there is a limit to the number of buckets an account can have.

    For this reason for now I am still serving user content from my servers, and have spent a ton of time trying to optimize page rendering. Sure, it's not like serving directly from the nearest CDN datacenter, but for now it looks speedy enough. :)

    1. 1

      If your users have already converted to customers then it's safe to say you're doing great and the speed is good enough.

      I focus most on optimizing speed for landing page specifically - so speed is not a barrier to conversion.

  4. 1

    If you’re going for fastest load time, prerender your markup — either ditch React or do what the sibling comment says and build it into HTML files beforehand.

    1. 1

      Thanks. I do prerender, but I called it "server side rendered" in the post. The sibling comment read about react-snap from my post :)

      1. 1

        Ah, sorry, I missed that somehow! 1am brain 🤪

  5. 1

    Just learnt about https://github.com/stereobooster/react-snap, will be interesting to use and see if it helps.

    1. 1

      I'm using react-snap as well, it's fairly low-setup. Try it out! :)

Trending on Indie Hackers
How I grew a side project to 100k Unique Visitors in 7 days with 0 audience 49 comments Competing with Product Hunt: a month later 33 comments Why do you hate marketing? 29 comments My Top 20 Free Tools That I Use Everyday as an Indie Hacker 16 comments $15k revenues in <4 months as a solopreneur 14 comments Use Your Product 13 comments