htmlnotes

DOCTYPE

DOCTYPE is an abbreviation for Document Type. A DOCTYPE is always associated to a DTD - for Document Type Definition.

A DTD defines how documents of a certain type should be structured (i.e. a button can contain a span but not a div), whereas a DOCTYPE declares what DTD a document supposedly respects (i.e. this document respects the HTML DTD).

For webpages, the DOCTYPE declaration is required. It is used to tell user agents what version of the HTML specifications your document respects. Once a user agent has recognized a correct DOCTYPE, it will trigger the no-quirks mode matching this DOCTYPE for reading the document. If a user agent doesn’t recognize a correct DOCTYPE, it will trigger the quirks mode.

The DOCTYPE declaration for the HTML5 standards is <!DOCTYPE html>.

Building blocks of HTML5

  • Semantics: HTML tags describe the content.
  • Styling: Customizing appearance of HTML tags
  • Connectivity: Communicate with the server in new and innovative ways.
  • Offline and storage: Allows webpages to store data on the client-side locally and operate offline more efficiently.
  • Multimedia: Makes video and audio first-class citizens in the Open Web.
  • 2D/3D graphics and effects: Allows a much more diverse range of presentation options.
  • Performance and integration: Provides greater speed optimization and better usage of computer hardware.
  • Device access: Allows for the usage of various input and output devices.

HTML specification vs browser implementation

HTML specifications such as HTML5 define a set of rules that a document must adhere to in order to be “valid” according to that specification. In addition, a specification provides instructions on how a browser must interpret and render such a document.

A browser is said to “support” a specification if it handles valid documents according to the rules of the specification. As of yet, no browser supports all aspects of the HTML5 specification (although all of the major browser support most of it), and as a result, it is necessary for the developer to confirm whether the aspect they are making use of will be supported by all of the browsers on which they hope to display their content. This is why cross-browser support continues to be a headache for developers, despite the improved specificiations.

  • HTML5 defines some rules to follow for an invalid HTML5 document (i.e., one that contains syntactical errors)

  • However, invalid documents may contain anything, so it’s impossible for the specification to handle all possibilities comprehensively.

  • Thus, many decisions about how to handle malformed documents are left up to the browser.

  • HTML 5.2 WWW Specifications

  • <header> is used to contain introductory and navigational information about a section of the page. This can include the section heading, the author’s name, time and date of publication, table of contents, or other navigational information.

  • <article> is meant to house a self-contained composition that can logically be independently recreated outside of the page without losing its meaning. Individual blog posts or news stories are good examples.

  • <section> is a flexible container for holding content that shares a common informational theme or purpose.

  • <footer> is used to hold information that should appear at the end of a section of content and contain additional information about the section. Author’s name, copyright information, and related links are typical examples of such content.

  • Other semantic elements are <form> and <table>

  • HTML 5 Semantic Elements

Multiple <header> elements

Yes to both. The W3 documents state that the tags represent the header(<header>) and footer(<footer>) areas of their nearest ancestor “section”. So not only can the page <body> contain a header and a footer, but so can every <article> and <section> element.

attribute vs property

Attributes

Attributes are defined in the HTML markup and provide initial values for elements. They are static and do not change once the page is loaded unless explicitly modified using JavaScript.

<input type="text" value="initial value" />

In this example, value="initial value" is an attribute.

Properties

Properties are part of the DOM and represent the current state of an element. They are dynamic and can change as the user interacts with the page or through JavaScript.

const inputElement = document.querySelector("input");
console.log(inputElement.value); // Logs the current value of the input element
inputElement.value = "new value"; // Changes the current value of the input element

In this example, value is a property of the inputElement object.

Key differences

  • Initialization: Attributes initialize DOM properties.
  • State: Attributes are static, while properties are dynamic.
  • Access: Attributes can be accessed using getAttribute and setAttribute methods, while properties can be accessed directly on the DOM object.
Example
<input id="myInput" type="text" value="initial value" />
const inputElement = document.getElementById("myInput");
 
// Accessing attribute
console.log(inputElement.getAttribute("value")); // "initial value"
 
// Accessing property
console.log(inputElement.value); // "initial value"
 
// Changing property
inputElement.value = "new value";
console.log(inputElement.value); // "new value"
console.log(inputElement.getAttribute("value")); // "initial value"

In this example, changing the value property does not affect the value attribute.

alt attribute

The alt attribute provides alternative information for an image if a user cannot view it. The alt attribute should be used to describe any images except those which only serve a decorative purpose, in which case it should be left empty.

  • Decorative images should have an empty alt attribute.

  • Web crawlers use alt tags to understand image content, so they are considered important for Search Engine Optimization (SEO).

  • Put the . at the end of alt tag to improve accessibility.

  • A good basis for accessibility

srcset attribute

You would use the srcset attribute when you want to serve different images to users depending on their device display width - serve higher quality images to devices with retina display enhances the user experience while serving lower resolution images to low-end devices increase performance and decrease data wastage (because serving a larger image will not have any visible difference). For example: <img srcset="small.jpg 500w, medium.jpg 1000w, large.jpg 2000w" src="..." alt=""> tells the browser to display the small, medium or large .jpg graphic depending on the client’s resolution. The first value is the image name and the second is the width of the image in pixels. For a device width of 320px, the following calculations are made:

  • 500 / 320 = 1.5625
  • 1000 / 320 = 3.125
  • 2000 / 320 = 6.25

If the client’s resolution is 1x, 1.5625 is the closest, and 500w corresponding to small.jpg will be selected by the browser.

If the resolution is retina (2x), the browser will use the closest resolution above the minimum. Meaning it will not choose the 500w (1.5625) because it is greater than 1 and the image might look bad. The browser would then choose the image with a resulting ratio closer to 2 which is 1000w (3.125).

srcsets solve the problem whereby you want to serve smaller image files to narrow screen devices, as they don’t need huge images like desktop displays do — and also optionally that you want to serve different resolution images to high density/low-density screens.

data- attributes

Before JavaScript frameworks became popular, developers used data- attributes to store extra data within the DOM itself, without other hacks such as non-standard attributes, extra properties on the DOM. It is intended to store custom data private to the page or application, for when there are no more appropriate attributes or elements.

Another common use case for data- attributes is to store information used by third-party libraries or frameworks. For example, the Bootstrap library uses data attributes to cause <button>s to trigger actions on a modal elsewhere on the page (example).

<button type="button" data-bs-toggle="modal" data-bs-target="#myModal">
  Launch modal
</button>
...
<div class="modal fade" id="myModal">Modal contents</div>

These days, using data- attributes is generally not encouraged. One reason is that users can modify the data attribute easily by using “inspect element” in the browser. The data model is better stored within JavaScript environment and have them kept in-sync with the DOM via virtual DOM reconciliation or two-way data binding possibly through a library or a framework.

However, one perfectly valid use of data attributes, is to add an identifier for end-to-end testing frameworks (e.g. Playwright, Puppeteer, Selenium), without adding classes or ID attributes just for tests which are primarily for other purposes. The element needs a way to be selected and something like data-test-id="my-element" is a valid way to do so without convoluting the semantic markup otherwise.

rel="noopener" attribute

The rel="noopener" is an attribute used in <a> elements (hyperlinks). It prevents pages from having a window.opener property, which would otherwise point to the page from where the link was opened and would allow the page opened from the hyperlink to manipulate the page where the hyperlink is.

load event and document DOMContentLoaded event

TL;DR
The `DOMContentLoaded` event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. The `load` event, on the other hand, fires when the entire page, including all dependent resources such as stylesheets and images, has finished loading.
document.addEventListener("DOMContentLoaded", function () {
  console.log("DOM fully loaded and parsed");
});
 
window.addEventListener("load", function () {
  console.log("Page fully loaded");
});

DOMContentLoaded event

The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. This event is useful when you want to execute JavaScript code as soon as the DOM is ready, without waiting for all resources to be fully loaded.

document.addEventListener("DOMContentLoaded", function () {
  console.log("DOM fully loaded and parsed");
});

load event

The load event is fired when the entire page, including all dependent resources such as stylesheets, images, and subframes, has finished loading. This event is useful when you need to perform actions that require all resources to be fully loaded, such as initializing a slideshow or performing layout calculations that depend on image sizes.

window.addEventListener("load", function () {
  console.log("Page fully loaded");
});

Key differences

  • Timing: DOMContentLoaded fires earlier than load. DOMContentLoaded occurs after the HTML is fully parsed, while load waits for all resources to be loaded.
  • Use cases: Use DOMContentLoaded for tasks that only require the DOM to be ready, such as attaching event listeners or manipulating the DOM. Use load for tasks that depend on all resources being fully loaded, such as image-dependent layout calculations.

mouseenter vs mouseover

TL;DR
The main difference lies in the bubbling behavior of `mouseenter` and `mouseover` events. `mouseenter` does not bubble while `mouseover` bubbles.

mouseenter events do not bubble. The mouseenter event is triggered only when the mouse pointer enters the element itself, not its descendants. If a parent element has child elements, and the mouse pointer enters child elements, the mouseenter event will not be triggered on the parent element again, it’s only triggered once upon entry of parent element without regard for its contents. If both parent and child have mouseenter listeners attached and the mouse pointer moves from the parent element to the child element, mouseenter will only fire for the child.

mouseover events bubble up the DOM tree. The mouseover event is triggered when the mouse pointer enters the element or one of its descendants. If have a parent element has child elements, and the mouse pointer enters child elements, the mouseover event will be triggered on the parent element again as well. If the parent element has multiple child elements, this can result in multiple event callbacks fired. If there are child elements, and the mouse pointer moves from the parent element to the child element, mouseover will fire for both the parent and the child.

Propertymouseentermouseover
BubblingNoYes
TriggerOnly when entering itselfWhen entering itself and when entering descendants

mouseenter event:

  • Does not bubble: The mouseenter event does not bubble. It is only triggered when the mouse pointer enters the element to which the event listener is attached, not when it enters any child elements.
  • Triggered once: The mouseenter event is triggered only once when the mouse pointer enters the element, making it more predictable and easier to manage in certain scenarios.

A use case for mouseenter is when you want to detect the mouse entering an element without worrying about child elements triggering the event multiple times.

mouseover Event:

  • Bubbles up the DOM: The mouseover event bubbles up through the DOM. This means that if you have an event listener on a parent element, it will also trigger when the mouse pointer moves over any child elements.
  • Triggered multiple times: The mouseover event is triggered every time the mouse pointer moves over an element or any of its child elements. This can lead to multiple triggers if you have nested elements.

A use case for mouseover is when you want to detect when the mouse enters an element or any of its children and are okay with the events triggering multiple times.

Example
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mouse Events Example</title>
    <style>
      .parent {
        width: 200px;
        height: 200px;
        background-color: lightblue;
        padding: 20px;
      }
      .child {
        width: 100px;
        height: 100px;
        background-color: lightcoral;
      }
    </style>
  </head>
  <body>
    <div class="parent">
      Parent Element
      <div class="child">Child Element</div>
    </div>
 
    <script>
      const parent = document.querySelector(".parent");
      const child = document.querySelector(".child");
 
      // Mouseover event on parent.
      parent.addEventListener("mouseover", () => {
        console.log("Mouseover on parent");
      });
 
      // Mouseenter event on parent.
      parent.addEventListener("mouseenter", () => {
        console.log("Mouseenter on parent");
      });
 
      // Mouseover event on child.
      child.addEventListener("mouseover", () => {
        console.log("Mouseover on child");
      });
 
      // Mouseenter event on child.
      child.addEventListener("mouseenter", () => {
        console.log("Mouseenter on child");
      });
    </script>
  </body>
</html>

Expected behavior

  • When the mouse enters the parent element:
    • The mouseover event on the parent will trigger.
    • The mouseenter event on the parent will trigger.
  • When the mouse enters the child element:
    • The mouseover event on the parent will trigger again because mouseover bubbles up from the child.
    • The mouseover event on the child will trigger.
    • The mouseenter event on the child will trigger.
    • The mouseenter event on the parent will not trigger again because mouseenter does not bubble.

event.preventDefault() vs event.stopPropagation()?

TL;DR
`event.preventDefault()` is used to prevent the default action that belongs to the event, such as preventing a form from submitting. `event.stopPropagation()` is used to stop the event from bubbling up to parent elements, preventing any parent event handlers from being executed.

event.preventDefault()

event.preventDefault() is a method that cancels the event if it is cancelable, meaning that the default action that belongs to the event will not occur. For example, this can be used to prevent a form from being submitted:

document.querySelector("form").addEventListener("submit", function (event) {
  event.preventDefault();
  // Form submission is prevented
});

event.stopPropagation()

event.stopPropagation() is a method that prevents the event from bubbling up the DOM tree, stopping any parent handlers from being notified of the event. This is useful when you want to handle an event at a specific level and do not want it to trigger handlers on parent elements:

document.querySelector(".child").addEventListener("click", function (event) {
  event.stopPropagation();
  // Click event will not propagate to parent elements
});

Key differences

  • event.preventDefault() stops the default action associated with the event.
  • event.stopPropagation() stops the event from propagating (bubbling) up to parent elements.

Use cases

  • Use event.preventDefault() when you want to prevent the default behavior of an element, such as preventing a link from navigating or a form from submitting.
  • Use event.stopPropagation() when you want to prevent an event from reaching parent elements, which can be useful in complex UIs where multiple elements have event listeners.

DOM

The DOM (Document Object Model) is a cross-platform API that treats HTML and XML documents as a tree structure consisting of nodes. These nodes (such as elements and text nodes) are objects that can be programmatically manipulated and any visible changes made to them are reflected live in the document. In a browser, this API is available to JavaScript where DOM nodes can be manipulated to change their styles, contents, placement in the document, or interacted with through event listeners.

  • The DOM was designed to be independent of any particular programming language, making the structural representation of the document available from a single, consistent API.
  • The DOM is constructed progressively in the browser as a page loads, which is why scripts are often placed at the bottom of a page, in the <head> with a defer attribute, or inside a DOMContentLoaded event listener. Scripts that manipulate DOM nodes should be run after the DOM has been constructed to avoid errors.
  • document.getElementById() and document.querySelector() are common functions for selecting DOM nodes.
  • Setting the innerHTML property to a new value runs the string through the HTML parser, offering an easy way to append dynamic HTML content to a node.
  • MDN docs for DOM

The DOM is structured as a hierarchical tree of nodes. Here are the main types of nodes:

  1. Document node: The root of the document tree. It represents the entire document.
  2. Element nodes: These represent HTML elements and form the bulk of the document tree.
  3. Attribute nodes: These are associated with element nodes and represent the attributes of those elements.
  4. Text nodes: These represent the text content within elements.
  5. Comment nodes: These represent comments in the HTML.
Example
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>
Document
  └── html
      ├── head
      │   └── title
      │       └── "Document"
      └── body
          ├── h1
          │   └── "Hello, World!"
          └── p
              └── "This is a paragraph."

JavaScript can be used to access and manipulate the DOM. Here are some common methods:

  • document.getElementById(id): Selects an element by its ID.
  • document.querySelector(selector): Selects the first element that matches a CSS selector.
  • element.appendChild(node): Adds a new child node to an element.
  • element.removeChild(node): Removes a child node from an element.
Example
// Select the <h1> element
const heading = document.querySelector("h1");
 
// Change its text content
heading.textContent = "Hello, DOM!";

HTML5 web storage

With HTML5, web pages can store data locally within the user’s browser. The data is stored in name/value pairs, and a web page can only access data stored by itself.

Differences between localStorage and sessionStorage regarding lifetime:

  • Data stored through localStorage is permanent: it does not expire and remains stored on the user’s computer until a web app deletes it or the user asks the browser to delete it.
  • sessionStorage has the same lifetime as the top-level window or browser tab in which the data got stored. When the tab is permanently closed, any data stored through sessionStorage is deleted.

Differences between localStorage and sessionStorage regarding storage scope: Both forms of storage are scoped to the document origin so that documents with different origins will never share the stored objects.

  • sessionStorage is also scoped on a per-window basis. Two browser tabs with documents from the same origin have separate sessionStorage data.

  • Unlike in localStorage, the same scripts from the same origin can’t access each other’s sessionStorage when opened in different tabs.

  • Earlier, this was done with cookies.

  • The storage limit is far larger (at least 5MB) than with cookies and its faster.

  • The data is never transferred to the server and can only be used if the client specifically asks for it.

  • W3Schools HTML5 Webstorage

Adding, removing, and modifying HTML elements using JavaScript

TL;DR

To add, remove, and modify HTML elements using JavaScript, you can use methods like createElement, appendChild, removeChild, and properties like innerHTML and textContent. For example, to add an element, you can create it using document.createElement and then append it to a parent element using appendChild. To remove an element, you can use removeChild on its parent. To modify an element, you can change its innerHTML or textContent.

// Adding an element
const newElement = document.createElement("div");
newElement.textContent = "Hello, World!";
document.body.appendChild(newElement);
 
// Removing an element
const elementToRemove = document.getElementById("elementId");
elementToRemove.parentNode.removeChild(elementToRemove);
 
// Modifying an element
const elementToModify = document.getElementById("elementId");
elementToModify.innerHTML = "New Content";

document.querySelector() vs document.getElementById()

TL;DR

document.querySelector() and document.getElementById() are both methods used to select elements from the DOM, but they have key differences. document.querySelector() can select any element using a CSS selector and returns the first match, while document.getElementById() selects an element by its ID and returns the element with that specific ID.

// Using document.querySelector()
const element = document.querySelector(".my-class");
 
// Using document.getElementById()
const elementById = document.getElementById("my-id");

document.querySelector()

  • Can select elements using any valid CSS selector, including class, ID, tag, attribute, and pseudo-classes
  • Returns the first element that matches the specified selector
  • More versatile but slightly slower due to the flexibility of CSS selectors
// Select the first element with the class 'my-class'
const element = document.querySelector(".my-class");
 
// Select the first <div> element
const divElement = document.querySelector("div");
 
// Select the first element with the attribute data-role='button'
const buttonElement = document.querySelector('[data-role="button"]');

document.getElementById()

  • Selects an element by its ID attribute
  • Returns the element with the specified ID
  • Faster and more efficient for selecting elements by ID, but less versatile
// Select the element with the ID 'my-id'
const elementById = document.getElementById("my-id");

Key differences

  • Selector type: document.querySelector() uses CSS selectors, while document.getElementById() uses only the ID attribute.
  • Return value: document.querySelector() returns the first matching element, whereas document.getElementById() returns the element with the specified ID.
  • Performance: document.getElementById() is generally faster because it directly accesses the element by ID, while document.querySelector() has to parse the CSS selector.

innerHTML vs textContent

innerHTML

  • Dynamically adding or updating HTML content
  • Rendering HTML tags and elements

Using innerHTML can expose your application to Cross-Site Scripting (XSS) attacks if you insert untrusted content. Always sanitize any user input before setting it as innerHTML.

textContent

textContent is a property that allows you to get or set the text content of an element. It ignores any HTML tags and renders them as plain text, making it safer for inserting user-generated content.

Example
const element = document.getElementById("example");
element.textContent = "<strong>Bold Text</strong>"; // This will render as plain text: <strong>Bold Text</strong>
  • Safely inserting user-generated content
  • Stripping HTML tags from a string

Performance considerations

textContent is generally faster than innerHTML because it does not parse and render HTML tags. It simply updates the text content of the element.

document.write()

document.write() can be used to write content directly to the document during the initial page load. This is one of the few scenarios where it might be appropriate, as it can be simpler and faster for very basic tasks.

Example
<!DOCTYPE html>
<html>
  <head>
    <title>Document Write Example</title>
  </head>
  <body>
    <script>
      document.write("<h1>Hello, World!</h1>");
    </script>
  </body>
</html>
  1. Educational purposes: document.write() is sometimes used in educational contexts to demonstrate basic JavaScript concepts. It provides a straightforward way to show how JavaScript can manipulate the DOM.

  2. Quick debugging:: For quick and dirty debugging, document.write() can be used to output variables or messages directly to the document. However, this is not recommended for production code.

    Example
    var debugMessage = "Debugging message";
    document.write(debugMessage);
  3. Legacy code: In some older codebases, you might encounter document.write(). While it’s not recommended to use it in new projects, understanding it can be useful for maintaining or refactoring legacy code.

  4. Why not use document.write()?

    • Overwrites the document: If called after the page has loaded, document.write() will overwrite the entire document, which can lead to loss of content and a poor user experience.

    • Better alternatives: Modern methods like innerHTML, appendChild(), and frameworks like React or Vue provide more control and are safer to use.

      Example
      // Using innerHTML
      document.getElementById("content").innerHTML = "<h1>Hello, World!</h1>";
       
      // Using appendChild
      var newElement = document.createElement("h1");
      newElement.textContent = "Hello, World!";
      document.getElementById("content").appendChild(newElement);

How do <iframe> on a page communicate?

TL;DR

<iframe> elements on a page can communicate using the postMessage API. This allows for secure cross-origin communication between the parent page and the iframe. The postMessage method sends a message, and the message event listener receives it. Here’s a simple example:

// In the parent page
const iframe = document.querySelector("iframe");
iframe.contentWindow.postMessage("Hello from parent", "*");
 
// In the iframe
window.addEventListener("message", (event) => {
  console.log(event.data); // 'Hello from parent'
});

The postMessage API is the most common and secure way for iframes to communicate with each other or with their parent page. This method allows for cross-origin communication, which is essential for modern web applications.

To send a message from the parent page to the iframe, you can use the postMessage method. Here’s an example:

// In the parent page
const iframe = document.querySelector("iframe");
iframe.contentWindow.postMessage("Hello from parent", "*");

In this example, the parent page selects the iframe and sends a message to it. The second parameter, '*', is the target origin. It specifies the origin of the target window. Using '*' means the message can be received by any origin, but for security reasons, it’s better to specify the exact origin.

To receive a message in the iframe, you need to add an event listener for the message event:

// In the iframe
window.addEventListener("message", (event) => {
  console.log(event.data); // 'Hello from parent'
});

The event object contains the data property, which holds the message sent by the parent page.

When using postMessage, it’s crucial to consider security:

  • Specify the target origin: Instead of using '*', specify the exact origin to ensure that only messages from trusted sources are received.
  • Validate the message: Always validate the message content to prevent malicious data from being processed.
Example with a specified target origin
// In the parent page
const iframe = document.querySelector("iframe");
const targetOrigin = "https://example.com";
iframe.contentWindow.postMessage("Hello from parent", targetOrigin);
 
// In the iframe
window.addEventListener("message", (event) => {
  if (event.origin === "https://parent.com") {
    console.log(event.data); // 'Hello from parent'
  }
});

In this example, the parent page sends a message only to https://example.com, and the iframe processes the message only if it comes from https://parent.com.

SEO friendly SPAs

A single page application (SPA) is a web application that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from the server. This results in a more fluid user experience, similar to a desktop application.

  • The application loads a single HTML page and dynamically updates it as the user interacts with the app
  • Uses AJAX or Fetch API to communicate with the server and update the page without a full reload
  • Often relies on client-side routing to manage different views or states within the app

Benefits

  • Faster interactions after the initial load
  • Reduced server load due to fewer full-page requests
  • Improved user experience with smoother transitions

How to make an SPA SEO-friendly

SPAs can be challenging for SEO because search engines may not execute JavaScript to render content. This can result in search engines indexing an empty or incomplete page.

Server-side rendering (SSR)

Server-side rendering involves rendering the initial HTML of the page on the server before sending it to the client. This ensures that search engines can index the fully rendered content.

  • React: Use Next.js, which provides built-in support for SSR
  • Vue.js: Use Nuxt.js, which also supports SSR out of the box

Example with Next.js:

import React from "react";
import { GetServerSideProps } from "next";
 
const Page = ({ data }) => {
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
};
 
export const getServerSideProps: GetServerSideProps = async () => {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();
 
  return {
    props: {
      data,
    },
  };
};
 
export default Page;

Static site generation (SSG)

Static site generation involves generating the HTML for each page at build time. This approach is suitable for content that doesn’t change frequently.

  • React: Use Next.js with its static generation capabilities
  • Vue.js: Use Nuxt.js with its static site generation feature

Example with Next.js:

import React from "react";
import { GetStaticProps } from "next";
 
const Page = ({ data }) => {
  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
};
 
export const getStaticProps: GetStaticProps = async () => {
  const res = await fetch("https://api.example.com/data");
  const data = await res.json();
 
  return {
    props: {
      data,
    },
  };
};
 
export default Page;

Pre-rendering with tools

Some tools can pre-render your SPA and serve the pre-rendered HTML to search engines.

  • Prerender.io: A service that pre-renders your JavaScript application and serves the static HTML to search engines
  • Rendertron: A headless Chrome rendering solution that can be used to pre-render your SPA

Further reading

Progressive rendering

Progressive rendering is the name given to techniques used to improve the performance of a webpage (in particular, improve perceived load time) to render content for display as quickly as possible.

It used to be much more prevalent in the days before broadband internet but it is still used in modern development as mobile data connections are becoming increasingly popular (and unreliable)!

Lazy loading of images

Images on the page are not loaded all at once. The image is only loaded when the user scrolls into/near the part of the page that displays the image.

  • <img loading="lazy"> is a modern way to instruct the browser to defer loading of images that are outside of the screen until the user scrolls near them.
  • Use JavaScript to watch the scroll position and load the image when the image is about to come on screen (by comparing the coordinates of the image with the scroll position).

Prioritizing visible content (or above-the-fold rendering)

Include only the minimum CSS/content/scripts necessary for the amount of page that would be rendered in the users browser first to display as quickly as possible, you can then use deferred scripts or listen for the DOMContentLoaded/load event to load in other resources and content.

Async HTML fragments

Flushing parts of the HTML to the browser as the page is constructed on the back end. More details on the technique can be found here.

Other modern techniques

Cache busting

Browsers have a cache to temporarily store files on websites so they don’t need to be re-downloaded again when switching between pages or reloading the same page. The server is set up to send headers that tell the browser to store the file for a given amount of time. This greatly increases website speed and preserves bandwidth.

However, it can cause problems when the website has been changed by developers because the user’s cache still references old files. This can either leave them with old functionality or break a website if the cached CSS and JavaScript files are referencing elements that no longer exist, have moved or have been renamed.

Cache busting is the process of forcing the browser to download the new files. This is done by naming the file something different to the old file.

A common technique to force the browser to re-download the file is to append a query string to the end of the file.

  • src="js/script.js" => src="js/script.js?v=2"

The browser considers it a different file but prevents the need to change the file name.

Adding elements

To add an HTML element, you can use the document.createElement method to create a new element and then append it to a parent element using appendChild.

// Create a new div element
const newDiv = document.createElement("div");
 
// Set its content
newDiv.textContent = "Hello, World!";
 
// Append the new element to the body
document.body.appendChild(newDiv);

You can also use insertBefore to insert the new element before an existing child element.

const parentElement = document.getElementById("parent");
const newElement = document.createElement("p");
newElement.textContent = "Inserted Paragraph";
const referenceElement = document.getElementById("reference");
parentElement.insertBefore(newElement, referenceElement);

Removing elements

To remove an HTML element, you can use the removeChild method on its parent element.

// Select the element to be removed
const elementToRemove = document.getElementById("elementId");
 
// Remove the element
elementToRemove.parentNode.removeChild(elementToRemove);

Alternatively, you can use the remove method directly on the element.

const elementToRemove = document.getElementById("elementId");
elementToRemove.remove();

Modifying elements

To modify an HTML element, you can change its properties such as innerHTML, textContent, or attributes.

// Select the element to be modified
const elementToModify = document.getElementById("elementId");
 
// Change its inner HTML
elementToModify.innerHTML = "New Content";
 
// Change its text content
elementToModify.textContent = "New Text Content";
 
// Change an attribute
elementToModify.setAttribute("class", "new-class");

You can also use methods like classList.add, classList.remove, and classList.toggle to modify the element’s classes.

const element = document.getElementById("elementId");
 
// Add a class
element.classList.add("new-class");
 
// Remove a class
element.classList.remove("old-class");
 
// Toggle a class
element.classList.toggle("active");

Multilingual sites

Designing and developing for multilingual sites is part of internationalization (i18n).

Serving a page with content available in multiple languages

Serving a page in different languages is one of the aspects of internationalization (i18n).

When an HTTP request is made to a server, the requesting user agent usually sends information about language preferences, such as in the Accept-Language header. The server can then use this information to return a version of the document in the appropriate language if such an alternative is available. The returned HTML document should also declare the lang attribute in the <html> tag, such as <html lang="en">...</html>.

To let a search engine know that the same content is available in different languages, <link> tags with the rel="alternate" and hreflang="..." attributes should be used. E.g. <link rel="alternate" hreflang="de" href="http://de.example.com/page.html" />.

  • Server-side rendering: The HTML markup will contain string placeholders and content for the specific language will be fetched from configuration in code or a translation service. The server then dynamically generates the HTML page with content in that particular language.
  • Client-side rendering: The appropriate locale strings will be fetched and combined with the JavaScript-based views.

Search Engine Optimization

  • Use the lang attribute on the <html> tag.
  • Include the locale in the URL (e.g en_US, zh_CN, etc).
  • Webpages should use <link rel="alternate" hreflang="other_locale" href="url_for_other_locale"> to tell search engines that there is another page at the specified href with the same content but for another language/locale.
  • Use a fallback page for unmatched languages. Use the “x-default” value: <link rel="alternate" href="url_for_fallback" hreflang="x-default" />.

Understanding the difference between locale vs language

Locale settings control how numbers, dates, and times display for your region: which may be a country, or a portion of country or may not even honor country boundaries.

Language can differ between countries

Certain languages, especially the widely-spoken languages have different “flavors” in different countries (grammar rules, spelling, characters). It’s important to differentiate languages for the target country and not assume/force one country’s version of a language for all countries which speak the language. Examples:

  • en: en-US (American English), en-GB (British English)
  • zh: zh-CN (Chinese (Simplified)), zh-TW (Chinese (Traditional))

Predict locale but don’t restrict

Servers can determine the locale/language of visitors via a combination of HTTP Accept-Language headers and IPs. With these, visitors can automatically select the best locale for the visitor. However, predictions are not foolproof (especially if visitors are using VPNs) and visitors should still be allowed to change their country/language easily without hassle.

Consider differences in the length of text in different languages

Some content can be longer when written in another language. Be wary of layout or overflow issues in the design. It’s best to avoid designing where the amount of text would make or break a design. Character counts come into play with things like headlines, labels, and buttons. They are less of an issue with free-flowing text such as body text or comments. For example, some languages, such as German and French, tend to use longer words and sentences than English, which can cause layout issues if you do not take this into account.

Language reading direction

Languages like English and French are written from left-to-right, top-to-bottom. However some languages, such as Hebrew and Arabic, are written from right to left. This can affect the layout of your site and the placement of elements on the page, so you must be careful to design your site in a way that accommodates different text directions.

Do not concatenate translated strings

Do not do anything like "The date today is " + date. It will break in languages with different word order. Use a template string with parameters substitution for each language instead. For example, look at the following two sentences in English and Chinese respectively: I will travel on {% date %} and 我会在{% date %}出发. Note that the position of the variable is different due to grammar rules of the language.

Formatting dates and currencies

Calendar dates are sometimes presented in different ways. Eg. “May 31, 2012” in the U.S. vs. “31 May 2012” in parts of Europe.

Do not put text in images

Putting text in raster-based images (e.g. png, gif, jpg, etc.), is not a scalable approach. Placing text in an image is still a popular way to get good-looking, non-system fonts to display on any computer. However, to support image text translation other languages, there needs to be a separate image created for each language which is not a scalable workflow for designers.

Be mindful of how colors are perceived

Colors are perceived differently across languages and cultures. The design should use color appropriately.