Adil Ismail

Web Rendering Patterns explained

Websites have changed drastically in the last decade. In the old days websites were plain simple HTML + CSS pages like wikipedia then they got a little interactive by using Javascript for animations, validations and so on. Nowadays we have websites that can build complex UI/UX diagrams like figma, doc builders like google docs, games all possible because of Javascript. In the past websites were backend heavy nowadays it is the opposite.

Web rendering patterns is the different approaches for generating and displaying content on web pages and in this article we will look at some of the common website rendering patterns like static, server side rendering, client side rendering and so-on.

Static Sites

Static sites are websites/pages that consist of prebuilt plain HTML, css & js. When a page is loaded, the browser requests for the assets and a webserver like Apache returns the pre built HTML, Css & js. It is the simplest and no serverside processing takes place using languages like PHP. No frameworks, no build tools just hand coded HTML,css files so easy to build and maintain. This pattern is still used today for simple landing pages, blogs etc.

Server Side Rendering(SSR)

SSR stands for server side rendering and it means the view rendering happens on the server and rendered HTML is send to the browser. For example PHP websites, PHP generates dynamic pages by fetching data from external sources like DB and using that data it will render the page. Example PHP page:

    <?php
      <html>
        <p>{{ db_user->name }}</p>
        <p>{{ db_user->age }}</p>
      </html>
    ?>

After PHP finishes processing this is what gets send to the browser:

    <html>
    <p>Adil</p>
    <p>24</p>
    </html>

It's not just PHP, frameworks like Ruby on rails and DJango also support this architecture. When we build websites this way using Laravel, Django, Rails etc, the website can also be called as Multi page apps. Javascript frameworks like Next.Js also support rendering pages in the server, we can do this by implementing getServerSideProps in the page component file. The data fetching logic inside getServerSideProps runs on the server each time a request is made to the page, rather than being pre-rendered at build time.

Example:

// This function gets called at request time: domain.com/ssr-page
export async function getServerSideProps() {
  // Fetch data from an API or database
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  // Pass data to the page component as props
  return {
    props: {
      data,
    },
  };
}

// Our SSR page component
export default function SSRPage({ data }) {
  return (
    <div>
      <h1>Server-Side Rendering Example</h1>
      <p>This data was fetched at build time:</p>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

Next.Js will convert this to HTML and send to the browser and hydrates the page again using client side JS (React) so that event handlers etc work as expected.

Client Side Rendering(CSR)

Client side rendering means page is built on the client side that is the browser. Libraries like React, Vue etc use this rendering pattern by default. When a React website is visited, the webserver responds with a mostly blank HTML, CSS if any and the Javascript code bundle which includes the libraries and application code(which instructs how to built the view). And once the JS is downloaded by the browser it gets executed, and a React app gets initialized, react router which is a third party library used in react for routing will check the current route eg: /blog, get it's corresponding component eg: Blog.jsx converts it to HTML DOM nodes and appends to the page, this is what happens at a high level. All the rendering work is done by Javascript at the client side. The problem with CSR is that rendering of our initial page will take some time and the JS can be very bulky(> 2MB) because it includes the code for all the pages, eg: even if the user requested /about-us page the code for all other pages like /dashboard etc will also be downloaded, there are ways to mitigate this though.

HTML code of a React app that just displays some images:

React app HTML file contents

As you can see the title, image tags are not present. It only get's added once React.Js initializes.

Static Site Generation(SSG)

Static site generation is method of creating websites by prerendering ahead of time that is at built time. This pattern is good to use when website content doesn't change often. There are many static site generators like Hugo, Next.JS, Gatsby and so on.

Lets see how Next.JS does this-

function About() {
  return <div>About</div>
}
 
export default About

If we defined a page like above in Next.js these automatically gets converted to static HTML at build time by Next.Js. If our static page depends upon some data, for example retrieved from a CMS then we need to define getStaticProps in our page component like below.

export default function Blog({ posts }) {
  // Render posts...
}
 
// This function gets called at build time
export async function getStaticProps() {
  // Call an external API endpoint to get posts
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

Incremental Static Regeneration (ISR)

If we build a blog with a massive amount of posts using SSG method then our build time will be really slow & resource intensive this is where Incremental static site re-generation comes. ISR has both the benefits of SSR and SSG, instead of rebuilding all the posts at build time we can do the rebuild at runtime as they are requested. Once a page is generated, it is cached and served to subsequent users directly from the cache. We can also set an expiration time for when Next.js should revalidate and update it. A sample ISR page:

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
 
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}
 
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))
 
  // We'll pre-render only these paths at build time.
  // { fallback: 'blocking' } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}
 
export default Blog

Not just Next.JS other frameworks like NuxtJs an Svelte kit also support this pattern.

← Back to home