The use case? Well, Ahmad wastes no time showing how to use the property to accomplish what used to require either (1) a wrapping element with hidden overflow around an image that’s sized and positioned inside that element or (2) the background-image route.
But with object-view-box we can essentially draw the image boundaries as we can with an SVG’s viewbox. So, take a plain ol’ <img> and call on object-view-box to trim the edges using an inset function. I’ll simply drop Ahmad’s pen in here:
Only supported in Chrome Canary for now, I’m afraid. But it’s (currently) planned to release in Chrome 104. Elsewhere:
Cloudinary (the media hosting and optimization service) has a brand new version (v3) of its WordPress plugin that has really nailed it. First, a high-level look at the biggest things this plugin does:
It takes over your media handling. Images and video are served by Cloudinary instead of your own server, which is good for a whole host of reasons. But don’t worry, your assets are also on your own server, so there is no lock-in.
It serves your images and video as performantly as possible. Everything is optimized, served in the best format, and use techniques like responsive images and lazy loading, all while providing a good loading experience. All together, those things are massive for performance.
It provides and image gallery block with lots of functionality.
Setting it up is as easy as copy and pasting from your Cloudinary account.
So, yes, you need a Cloudinary account. You can check out the programmable media plans here. There is a free tier that will likely work for many sites and paid plans that will cover sites that have heavy media needs, of which you’ll likely find the pricing amicable. Once you have your account, you pop the connection string (from your dashboard) into this quick onboarding wizard and you’re basically done. The default settings are good.
You could do literally nothing else and the plugin will work its magic, but it’s fun to look through all the settings.
Here are the general settings:
Those two (default) settings are important. Auto sync is nice in that all your images (even your entire existing media library) is synced up to Cloudinary and stays in sync. This is necessary to host your images (and do fancy optional stuff like “transforms”) but you could otherwise think of it as a backup. When you use “Cloudinary and WordPress” as the Storage setting, it means that media will be uploaded to your own server and Cloudinary. That’s what I would highly recommend, but if you’re in a situation where, say, you have very limited or no storage on your WordPress host, you could have the images go straight to Cloudinary (only).
In the Image settings, you can see two of Cloudinary’s most powerful weapons: f_auto and q_auto, standing for “auto image formatting” and “auto quality compression.” Those are defaults I’d highly recommend leaving alone. It means that any browser on any device gets the best possible format of the image, and Cloudinary adjusts the quality as appropriate for that image. Cloudinary has very good tech for doing this, so let it do it.
The “doing images right” checklist is a thing.
Remember, we blogged it just recently. Host them on a CDN. Optimze them. Serve them in the best possible format for the requesting browser. Use responsive images. Lazy load them. None of those things are trivial, and that’s just a partial list. The good news is: this plugin does all that stuff for you, does it well, and does it without you having to think too much about it.
I like seeing the output. This is where the rubber meets the road. From this I can see that responsive images are implemented correctly and lots of different sizes are available. I can see the the image sources are pointing at the Cloudinary CDN. I can see lazy loading is implemented and working. I can see the width and height attributes are there as they should be to ensure space is reserved for the images during loading. This is everything.
It goes the extra mile by hosting the images the used by the theme as well.
Heck, it replaces CSS background-images in your theme’s stylesheet with Cloudinary-hosted versions. That’s… amazing. There must be some real clever WordPress filter stuff going on.
The Gallery Block is just gravy.
I like seeing this in there:
Why? It shows that this plugin is part of modern WordPress. Block editor WordPress. The block itself is simple, but useful. It shows images in a variety of useful layouts with a “lightbox”-like effect (wow, it’s been a long time since I’ve typed the word lightbox). Hey, sometimes you just need a dang image gallery and you might as well use one that is well done.
Who am I to say?
Just a lowly blogger, I suppose. But I can tell you I’ve been watching this evolve for quite a while. A ways back, I had implemented a hand-rolled Cloudinary integration here on CSS-Tricks because I wanted all this stuff. I ultimately had to give up on it as it was more technical debt than I could maintain.
The previous versions of the WordPress plugin were better, but it’s not until now, v3, where this integration is truly nailed.
Shortly after that time I tore down my custom integration, I blogged “Workflow Considerations for Using an Image Management Service” and outlined what I thought the (rather high) bar would be for integrating a third-party image host. It was a lot to ask, and I wasn’t really sure if anyone would find the incentive and motivation to do it all. Well, Cloudinary has done it here. This is as perfect a media management plugin as I could imagine.
This post was in progress before Automattic acquired Frontity and its entire team. According to Frontity’s founders, the framework will be transitioned into a community-led project and leave the project in “a stable, bug-free position” with documentation and features. Like other open-source community projects, Frontity will remain free as it has been, with opportunities to contribute to the project and make it an even better framework for decoupled WordPress. More detail is found in this FAQ page.
In my previous article, we created a headless WordPress site with Frontity and briefly looked at its file structure. In this companion article, we will go into a deep dive of the @frontity/mars-theme package, or Mars Theme, with a step-by-step walkthrough on how to customize it to make our own. Not only is the Mars Theme a great starter, it’s Frontity’s default theme — sort of like WordPress Twenty Twenty-One or the like. That makes it a perfect starting point for us to get hands-on experience with Frontity and its features.
Specifically, we will look at the fundamental parts of Frontity’s Mars Theme, including what they call “building blocks” as well as the different components that come with the package. We’ll cover what those components do, how they work, and finally, how styling works with examples.
Let’s revisit the file structure of the Frontity project we made in the last article as that shows us exactly where to find Frontity’s building blocks, the frontity.settings.js, and package.json and packages/mars-theme folder. We covered these is great detail before but, in particular, the package.json file gives us a lot of information about the project, like the name, description, author, dependencies, etc. Here’s what that file includes:
frontity: this is the main package that includes all the methods used in Frontity app development. It’s also where the CLI lives.
@frontity/core: This is the most important package because it takes care of all the bundling, rendering, merging, transpiling, serving, etc. We don’t need to access to it in order to develop a Frontity app. The full list is captured in the Frontity docs.
@frontity/wp-source:This package connects to the WordPress REST API of our site and fetches all the data needed in the Mars Theme.
@frontity/tiny-router:This package handles window.history and helps us with routing.
@frontity/htmal2react:This package converts HTML to React, working with processors that match HTML portions while replacing them with React components.
Frontity core, or @frontity/package (also referred as Frontity’s building block), is composed of useful React component libraries in its @frontity/components package, which exports helpful things like Link, Auto Prefetch, Image, Props, Iframe, Switch, and other functions, objects, etc., that can be directly imported into Frontity project components. A more detailed description of these components—including syntax info use cases—is in this package reference API.
When starting frontity, all the packages defined in frontity.settings.js are imported by @frontity/file-settings and the settings and exports from each package are merged by @frontity/core into a single store where you can access the state and actions of the different packages during development using @frontity/connect, the frontity state manager.
Next up, we’re familiarizing ourselves with how these building blocks, utilities and exports are used in the Mars Theme package to create a functioning Frontity project with a headless WordPress endpoint.
Section 1: Digging into the Mars Theme
Before discussing styling and customizing let’s briefly familiarize ourselves with the Mars Theme (@frontity/mars-theme) file structure and how it is put together.
The Mars Theme has three important component files: /src/index.js file, src/list/index.js and src/components/index.js. Frontity’s documentation is a great resource for understanding the Mars Theme, with especially great detail on how different Mars Theme components are defined and connected together in a Frontity site. Let’s start familiarizing ourselves with the theme’s three most important components: Root, Theme and List.
Theme Root component (/src/index.js)
The src/index.js file, also known as the theme’s Root, is one of the most important Mars Theme components. The Root serves as an entry point that targets <div id="root"> in the site markup to inject the roots of all the installed packages required to run a Frontity project. A Frontity theme exports a root and other required packages in the DOM as shown in the following use case example from the Frontity documentation:
This is everything the package pulls in when initializing the Root component:
// mars-theme/src/components/index.js import Theme from "./components"; // import processor libraries import image from "@frontity/html2react/processors/image"; import iframe from "@frontity/html2react/processors/iframe"; import link from "@frontity/html2react/processors/link"; const marsTheme = { // The name of the extension name: "@frontity/mars-theme", // The React components that will be rendered roots: { /** In Frontity, any package can add React components to the site. * We use roots for that, scoped to the `theme` namespace. */ theme: Theme, }, state: { /** State is where the packages store their default settings and other * relevant state. It is scoped to the `theme` namespace. */ theme: { autoPrefetch: "in-view", menu: [], isMobileMenuOpen: false, featured: { showOnList: false, showOnPost: false, }, }, }, /** Actions are functions that modify the state or deal with other parts of * Frontity-like libraries. */ actions: { theme: { toggleMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen; }, closeMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = false; }, }, }, /** The libraries that the extension needs to create in order to work */ libraries: { html2react: { /** Add a processor to `html2react` so it processes the `<img>` tags * and internal link inside the content HTML. * You can add your own processors too. */ processors: [image, iframe, link], }, }, }; export default marsTheme;
The Mars Theme root component exports packages that includes any of the roots, fills, state, actions and libraries elements. More detailed information on Root can be found in this Frontity doc.
Theme component (/src/components/index.js)
The Frontity Theme component is its main root level component that is exported by the Theme namespace (lines 12-16, highlighted in the previous example. The Theme component is wrapped with the @frontity/connect function (line 51, highlighted below) which provides access to its state, actions and libraries props from the Root component instance and allows Theme component to read the state, manipulate through actions, or use code from other features packages in the libraries.
// mars-theme/src/components/index.js import React from "react" // Modules from @emotion/core, @emotion/styled, css, @frontity/connect, react-helmet import { Global, css, connect, styled, Head } from "frontity"; import Switch from "@frontity/components/switch"; import Header from "./header"; import List from "./list"; import Post from "./post"; import Loading from "./loading"; import Title from "./title"; import PageError from "./page-error"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> {/* Add some metatags to the <head> of the HTML with react-helmet */} <Title /> <Head> <meta name="description" content={state.frontity.description} /> <html lang="en" /> </Head> {/* Add some global styles for the whole site, like body or a's. Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> {/* Render Header component. Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> {/* Add the main section. It renders a different component depending on the type of URL we are in. */} <Main> <Switch> <Loading when={data.isFetching} /> <List when={data.isArchive} /> <Post when={data.isPostType} /> <PageError when={data.isError} /> </Switch> </Main> </> ); }; export default connect(Theme); {/* define Global styles and styled components used Theme component here */} const globalStyles = css` body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; } a, a:visited { color: inherit; text-decoration: none; } `; const HeadContainer = styled.div` // ... `; const Main = styled.div` // ... `;
This example is pulled directly from the Mars Theme’s /src/components/index.js component, which we imported with connect from frontity (line 4, above). We are using state.source.get() to retrieve data to be rendered from the current path (lines 39-46, highlighted above); for example, List, Post and other components.
Section 2: Working with the List component
What we just looked at are the theme-level components in Frontity’s Mars Theme. You may have noticed that those components import additional components. Let’s look at a specific one of those, the List component.
The List component is exported by src/components/list/index.js which uses @loadable/components to split the List component code in such a way that the component only loads when a user clicks a List view; otherwise it won’t render at all, like when a Post view is clicked instead.
// src/components/list/index.js import { loadable } from "frontity"; // Codesplit the list component so it's not included if the users // load a post directly. export default loadable(() => import("./list"));
In this example, Frontity utilizes loadble functions (integrated from Loadable components) for code splitting which loads a component asynchronously and separates code into different bundles that are dynamically loaded at run time. Frontity’s core package API reference goes into much more detail.
Displaying lists of posts
To display a list of posts in an archive page, we first have to look Frontity src/components/list/list.js component. As the name suggests, the List component renders lists of posts using state.source.get(link) and its items field (lines 22-25, highlighted below).
// src/components/list/list.js import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination"; const List = ({ state }) => { // Get the data of the current list. const data = state.source.get(state.router.link); return ( <Container> {/* If the list is a taxonomy, we render a title. */} {data.isTaxonomy && ( <Header> {data.taxonomy}: {state.source[data.taxonomy][data.id].name} </Header> )} {/* If the list is an author, we render a title. */} {data.isAuthor && ( <Header>Author: {state.source.author[data.id].name}</Header> )} {/* Iterate over the items of the list. */} {data.items.map(({ type, id }) => { const item = state.source[type][id]; // Render one Item component for each one. return <Item key={item.id} item={item} />; })} <Pagination /> </Container> ); }; export default connect(List);
In the code example above, the connect function is imported by frontity in line 2 and is wrapped around the exported connect(List) component in line 31 (the last line). Two other components, list-item.js and pagination.js are also imported. Let’s look at those next!
Here’s what we have for list-item.js:
// src/components/list/list-item.js import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media"; const Item = ({ state, item }) => { const author = state.source.author[item.author]; const date = new Date(item.date); return ( <article> {/* Rendering clickable post Title */} <Link link={item.link}> <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} /> </Link> <div> {/* If the post has an author, we render a clickable author text. */} {author && ( <StyledLink link={author.link}> <AuthorName> By <b>{author.name}</b> </AuthorName> </StyledLink> )} {/* Rendering post date */} <PublishDate> {" "} on <b>{date.toDateString()}</b> </PublishDate> </div> {/* If the want to show featured media in the * list of featured posts, we render the media. */} {state.theme.featured.showOnList && ( <FeaturedMedia id={item.featured_media} /> )} {/* If the post has an excerpt (short summary text), we render it */} {item.excerpt && ( <Excerpt dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} /> )} </article> ); }; // Connect the Item to gain access to `state` as a prop export default connect(Item);
The Item component renders the preview of a blog post with clickable post title (lines, 12-14, highlighted above), author name (lines 19-21, highlighted above) and published date (lines: 25-28, highlighted above) along with <FeaturedMedia /> which serves as a post’s optional featured image.
Paginating a list of posts
Let’s look at thePagination component that was rendered earlier in the List component by the src/components/list/pagination/js that follows:
// src/components/list/pagination.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "../link"; const Pagination = ({ state, actions }) => { // Get the total posts to be displayed based for the current link const { next, previous } = state.source.get(state.router.link); // Pre-fetch the the next page if it hasn't been fetched yet. useEffect(() => { if (next) actions.source.fetch(next); }, []); return ( <div> {/* If there's a next page, render this link */} {next && ( <Link link={next}> <Text>← Older posts</Text> </Link> )} {previous && next && " - "} {/* If there's a previous page, render this link */} {previous && ( <Link link={previous}> <Text>Newer posts →</Text> </Link> )} </div> ); }; /** * Connect Pagination to global context to give it access to * `state`, `actions`, `libraries` via props */ export default connect(Pagination);
The Pagination component is used so that users can paginate between lists of posts — you know, like navigating forward from Page 1 to Page 2, or backward from Page 2 to Page 1. The state, actions, libraries props are provided by the global context that wraps and exports them with connect(Pagination).
Displaying single posts
The Post component displays both single posts and pages. Indeed, structurally both are the same except, in posts, we usually display meta data (author, date, categories etc). Meta data isn’t usually used in pages.
In this Post component, conditional statements are rendered only if the post object contains data (i.e. data.isPost) and a featured image is selected in sate.theme.featured in the theme’s root component:
// src/components/post.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "./link"; import List from "./list"; import FeaturedMedia from "./featured-media"; const Post = ({ state, actions, libraries }) => { // Get information about the current URL. const data = state.source.get(state.router.link); // Get the data of the post. const post = state.source[data.type][data.id]; // Get the data of the author. const author = state.source.author[post.author]; // Get a human readable date. const date = new Date(post.date); // Get the html2react component. const Html2React = libraries.html2react.Component; useEffect(() => { actions.source.fetch("/"); {/* Preloading the list component which runs only on mount */} List.preload(); }, []); // Load the post, but only if the data is ready. return data.isReady ? ( <Container> <div> <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} /> {/* Only display author and date on posts */} {data.isPost && ( <div> {author && ( <StyledLink link={author.link}> <Author> By <b>{author.name}</b> </Author> </StyledLink> )} <DateWrapper> {" "} on <b>{date.toDateString()}</b> </DateWrapper> </div> )} </div> {/* Look at the settings to see if we should include the featured image */} {state.theme.featured.showOnPost && ( <FeaturedMedia id={post.featured_media} /> )} {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */} <Content> <Html2React html={post.content.rendered} /> </Content> </Container> ) : null; }; {/* Connect Post to global context to gain access to `state` as a prop. */} export default connect(Post);
Section 3: Links, menus, and featured images
We just saw how important the List component is when it comes to displaying a group of posts. It’s what we might correlate to the markup we generally use when working with the WordPress loop for archive pages, latest posts feeds, and other post lists.
There are a few more components worth looking at before we get into Mars Theme styling.
The Link component (src/components/link.js)
The following MarsLink component comes from src/components/link.js, which is a wrapper on top of the {@link Link} component. It accepts the same props as the {@link Link} component.
// src/components/link.js import { connect, useConnect } from "frontity"; import Link from "@frontity/components/link"; const MarsLink = ({ children, ...props }) => { const { state, actions } = useConnect(); /** A handler that closes the mobile menu when a link is clicked. */ const onClick = () => { if (state.theme.isMobileMenuOpen) { actions.theme.closeMobileMenu(); } }; return ( <Link {...props} onClick={onClick} className={className}> {children} </Link> ); }; // Connect the Item to gain access to `state` as a prop export default connect(MarsLink, { injectProps: false });
As explained in this tutorial, the Link component provides a link attribute that takes a target URL as its value. Quoting from the doc: it outputs an <a> element into the resulting HTML, but without forcing a page reload which is what would occur if you simply added an <a> element instead of using the Link component.
Frontity menu (src/components/nav.js)
Earlier, we defined values for menu items in the frontity.settings.js file. In the Nav component (located in src/components/nav/js) those menu item values are iterated over, match their page url, and display the component inside the Header component.
// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link"; const Nav = ({ state }) => ( <NavContainer> // Iterate over the menu exported from state.theme and menu items value set in frontity.setting.js {state.theme.menu.map(([name, link]) => { // Check if the link matched the current page url const isCurrentPage = state.router.link === link; return ( <NavItem key={name}> {/* If link URL is the current page, add `aria-current` for a11y */} <Link link={link} aria-current={isCurrentPage ? "page" : undefined}> {name} </Link> </NavItem> ); })} </NavContainer> ); // Connect the Item to gain access to `state` as a prop export default connect(Nav);
Featured Image component (/src/components/featured-media.js)
In Frontity, featured media items values are defined in the Root component ‘s theme.state.featured line that we discussed earlier. Its full code is available in the /src/components/featured-media.js component file.
Now that we’re more familiar with the Mars Theme, as well as its building blocks, components, and functions, we can move into the different approaches that are available for styling the Mars Theme front-end.
As we move along, you may find this Frontity doc a good reference for the various styling approaches we cover.
First off, Frontity provides us with reusable components made with with styled-components, and Emotion, a CSS library for styling components in JavaScript, right out of the box. Emotion is popular with React and JavaScript developers, but not so much in the WordPress community based on what I’ve seen. CSS-Tricks has covered CSS-in-JS in great detail including how it compares with other styling, and this video provides background information about the library. So, knowing that both styled-components and Emotion are available and ready to use is nice context as we get started.
I am new to the CSS-in-JS world, except for some general reading on it here and there. I was exposed to CSS-in-JS styling in a Gatsby project, but Gatsby provides a bunch of other styling options that aren’t readily available in Frontity or the Mars Theme. That said, I feel I was able to get around that lack of experience, and what I learned from my discovery work is how I’m going to frame things.
So, with that, we are going to visit a few styling examples, referencing Frontity’s styling documentation as we go in order to familiarize ourselves with even more information.
Using styled-components
As the name suggests, we need a component in order to style it. So, first, let’s create a styled-component using Emotion’s styled function.
Let’s say we want to style a reusable <Button /> component that’s used throughout our Frontity project. First, we should create a <Button /> component (where its div tag is appended with a dot) and then call the component with a template literal for string styles.
Now this <Button /> component is available to import in other components. Let’s look specifically at the Mars Theme<Header /> component to see how the styled-component is used in practice.
// mars-theme/src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import MobileMenu from "./menu"; const Header = ({ state }) => { return ( <> <Container> // This component is defined later <StyledLink link="/"> // This component is defined later <Title>{state.frontity.title}</Title> // This component is defined later </StyledLink> // ... </Container> </> ); }; // Connect the Header component to get access to the `state` in its `props` export default connect(Header); // Defining the Container component that is a div with these styles const Container = styled.div` width: 848px; max-width: 100%; box-sizing: border-box; padding: 24px; color: #fff; display: flex; flex-direction: column; justify-content: space-around; `; // Defining Title component that is h2 with these styles const Title = styled.h2` margin: 0; margin-bottom: 16px; `; // Defining StyledLink component that is a third-party Link component const StyledLink = styled(Link)` text-decoration: none; `;
In the above code example, the <StyledLink /> component (lines 39-41, highlighted above) is used to style another component, <Link />. Similarly. the <Container /> and <Title /> styled-components are used to style the site title and the site’s main container width.
The Emotion docs describe how a styled component can be used as long as it accepts className props. This is a useful styling tool that can be extended using a variable as shown in the following example below from Frontity’s documentation:
// mars-theme/src/components/header.js // ... // We create a variable to use later as an example Const LinkColor = "green"; // ... // Defining StyledLink component that is a third-party Link component const StyledLink = styled(Link)` text-decoration: none; Background-color: $ {linkColor}; `;
The styled component above is used extensively in the Mars Theme. But before we go further, let’s look at using a CSS prop to style components.
Using a CSS prop
The css prop is available as a template literal for inline styling from the Frontity core package. It is similar to styled-components, except css does not return a React component but rather a special object that can be passed to a component through the css prop.
/* Using as CSS prop */ import { css } from "frontity"; const PinkButton = () => ( <div css={css`background: pink`}> My Pink Button </div> );
See that? We can style a component inline using the css prop on a component. Additional use case examples are available in the Emotion docs.
Using the <Global /> component
<Global /> is a React component that allows to us create site-wide general styles, though Frontity does not optimize it for performance. Global styles should be added to the <Theme /> root component.
// packages/mars-theme/src/components/index.js // ... import { Global, css, styled } from "frontity"; import Title from "./title"; import Header from "./header"; // ... // Theme root const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> {/* Add some metatags to the <head> of the HTML. */} <Title /> // ... {/* Add global styles */} <Global styles={globalStyles} /> {/* Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> // ... </> ); }; export default connect(Theme); const globalStyles = css` body { margin: 0; font-family: -apple-system, "Helvetica Neue", Helvetica, sans-serif; } a, a:visited { color: inherit; text-decoration: none; } `; const HeadContainer = styled.div` // ... `;
The <Global /> component has a style attribute that takes a css function as its value and consists of standard CSS inside back ticks (lines 35-45, highlighted above) as template literals. Frontity recommends using global styles for globally-used HTML tags, like <html>, <body>, <a>, and <img>.
I did a lot of research heading into my Mars Theme project and thought I’d share some of the more useful resources I found for styling Frontity themes:
Official Frontity themes. In addition to the default Mars Theme, Frontity has a ready-to-use package that ports the default WordPress Twenty Twenty theme in its entirety to a Frontity project. You will notice in the next section that my style customizations were inspired by this great learning resource.
Community themes. At this time of this writing, there are a grand total of nine Frontity community members who contributed fully functional theme packages. Those themes can be cloned into your own project and customized according to your needs. Likewise, many of the sites included in the Frontity showcase have GitHub repository links, and just as we can copy or pick up design tips from WordPress themes, we can use these resources to customize our own Frontity theme by referencing these packages.
Creating your own theme from scratch.The Frontity tutorial site has an excellent step-by-step guide to create your own fully working and functional theme package from scratch. Although it’s a little time consuming to go through it all, it is the best approach to fully understand a Frontity site project.
Now that we have covered the more commonly used Frontity styling techniques, let’s apply what we’ve learned to start customizing our Mars Theme project.
Section 5: Customizing the Frontity Mars Theme
I’m going to share one of my working Frontity projects, where I took the Mars Theme as a base and modified it with the resources we’ve covered so far. Because this is my learning playground, I took time to learn from Frontity default themes, community themes and Frontity showcase sites.
So here are examples of how I customized Frontity’s Mars Theme for my headless WordPress site project.
First, I wanted to change the @frontity/mars-theme package name to something different. It’s a good idea to change the package name and make sure all of the dependencies in the package file are up to date. Luis Herrera outlines the required steps for renaming the Mars Theme package in this frontity community forum, which I used as a reference to go from @fontity/mars-theme package to @frontity/labre-theme.
So, open up the package.json file and change the name property on line 2. This is the name of the package that gets used throughout the project.
I renamed my project from mars-theme to labre-theme in my package.json file,.
We should also update the name of the project folder while we’re at it. We can do that on line 25. I changed mine from ./package/mars-theme to ./package/labre-theme. Now, the theme package is properly listed as a dependency and will be imported to the project.
Our frontity-settings.js file needs to reflect the name change. So, let’s open that up and:
rename the package name on line 13 (I changed mine from @frontity/mars-theme to @frontity/labre-theme), and
rename the name on line 3 (I changed mine from mars-demo to labre-demo).
Next up, we want to re-initialize the project with these changes. We should delete the node_modules folder with rm -rf node_modules in a terminal and reinstall the npm package with yarn install. Once the npm package is reinstalled, everything gets properly linked internally and our Frontity project runs just fine without any errors.
Refactoring navigation with dynamic menu fetching
As we discussed earlier, Frontity menu items are either hard-coded in the frontity.setting.js file or in index.js component that’s stored in the Frontity state. However, WordPress can dynamically fetch the Frontity menu. In fact, Frontity just so happens to have a YouTube video onthe subject. Let me break down the key steps here.
The first step is to install the WP-REST-API V2 Menus plugin in WordPress. The plugin is freely available in the WordPress Plugin Directory, which means you can find it and activate it directly from the WordPress admin.
Why do we need this plugin? It extends the new routes to all the registered WordPress menus to the REST API (e.g. /menus/v1/menus/<slug>).
If we check our project site at /wp-json/menu/v1/menus, it should display our selected menu items in the JSON. We can get the menu items with the menu item’s slug property.
Next, let’s use the menuHandler function from the tutorial. Create a new menu-handler.js file at src/components/handler/menu-handler.js and paste in the following code:
// src/components/handler/menu-handler.js const menuHandler = { name: "menus", priority: 10, pattern: "/menu/:slug", func: async ({ link, params, state, libraries }) => { console.log("PARAMS:", params); const { slug } = params; // Fetch the menu data from the endpoint const response = await libraries.source.api.get({ endpoint: `/menus/v1/menus/$ {slug}`, }); // Parse the JSON to get the object const menuData = await response.json(); // Add the menu items to source.data const menu = state.source.data[link]; console.log(link); Object.assign(menu, { items: menuData.items, isMenu: true, }); }, }; export default menuHandler;
This menuHandler function is only executed if the pattern value (i.e. /menu/:slug) matches. Now let’s update our /src/index.js root component so it imports the handler:
// src/index.js import Theme from "./components"; import image from "@frontity/html2react/processors/image"; import iframe from "@frontity/html2react/processors/iframe"; import link from "@frontity/html2react/processors/link"; import menuHandler from "./components/handlers/menu-handler"; const labreTheme = { // ... state: { theme: { autoPrefetch: "in-view", menu: [], {/* Add menuURL property with menu slug as its value */} menuUrl: "primary-menu", isMobileMenuOpen: false, // ... }, }, /** Actions are functions that modify the state or deal with other parts of * Frontity-like libraries */ actions: { theme: { toggleMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen; }, closeMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = false; }, {/* Added before SSR action */} beforeSSR: async ({ state, actions }) => { await actions.source.fetch(`/menu/$ {state.theme.menuUrl}/`); }, }, }, libraries: { // ... {/* Added menuHandler source */} source: { handlers: [menuHandler], }, }, }; export default labreTheme;
Add an array of handlers under the source property and fetch data before the beforeSSR function. It does not fetch but does match the menu-handler slug, which means menuHandler() is executed. That puts the menu items into state and they become available to manipulate.
Please note that we have added a new menuUrl property here (line 15 above) which can be used as a variable at our endpoint in handlers, as well as the nav.js component. Then, changing the value of menuUrl in the index.js root component, we could display another menu.
Let’s get this data into our theme through state and map with menu-items to display on the site.
// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link"; /** Navigation Component. It renders the navigation links */ const Nav = ({ state }) => { {/* Define menu-items constants here */} const items = state.source.get(`/menu/$ {state.theme.menuUrl}/`).items; return ( <NavContainer> {items.map((item) => { return ( <NavItem key={item.ID}> <Link link={item.url}>{item.title}</Link> </NavItem> ); })} </NavContainer> ); }; export default connect(Nav); const NavContainer = styled.nav` list-style: none; // ...
If we change our menu slug here and in index.js, then we get a different menu. To view dynamic menu items in mobile view, we should similarly update menu-modal.js components as well.
Additionally, the tutorial describes how to fetch nested menus as well, which you can learn from the tutorial video, starting at about 18:09.
Modifying the file structure
I decided to restructure my Labre (formerly known as Mars) theme folder. Here’s how it looks after the changes:
As you can see, I added separate folders for pages, styles, headers, posts, and images. Please take a note that we have to update file paths in index.js and other related components anytime we change the way files and folders are organized. Otherwise, they’ll be pointing to nothing!
Adding a custom footer component
You may have noticed that the original Mars Theme folder structure includes neither a footer component, nor a separate page component. Let’s make those components to demonstrate how our new folder structure works.
We can start with the page component. The Mars Theme generates both pages and posts with the posts.js component by default — that’s because pages and posts are essentially the same except that posts have meta data (e.g. authors, date, etc.) and they can get away with it. But we can separate them for our own needs by copying the code in posts.js and pasting it into a new pages.js file in our /pages folder.
// src/components/pages/page.js import React, { useEffect } from "react"; import { connect, styled } from "frontity"; import List from "../list"; const Page = ({ state, actions, libraries }) => { // Get information about the current URL. const data = state.source.get(state.router.link); // Get the data of the post. const page = state.source[data.type][data.id]; // ... // Load the page, but only if the data is ready. return data.isReady ? ( <Container> <div className="post-title"> <Title dangerouslySetInnerHTML={{ __html: page.title.rendered }} /> </div> {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */} <Content> <Html2React html={page.content.rendered} /> </Content> </Container> ) : null; }; // Connect the Page component to get access to the `state` in its `props` export default connect(Page); // Copy styled components from post.js except, DateWrapper const Container = styled.div` width: 90vw; width: clamp(16rem, 93vw, 58rem); margin: 0; padding: 24px; ` // ..
All we did here was remove the meta data from post.js (lines 31-34 and 55-76) and the corresponding styled components. Just as we did with the Mars Theme /list folder, we should export the loadable function in both the pages and posts folders to code split the <List /> component. This way, the <List /> component isn’t displayed if a user is on a single post.
// src/components/pages/index.js import { loadable } from "frontity"; /** Codesplit the list component so it's not included * if the users load a post directly. */ export default loadable(() => import("./page"));
Next, we should update path url of /src/components/index.js component as shown below:
// src/components/index.js import { Global, css, connect, styled, Head } from "frontity"; import Switch from "@frontity/components/switch"; import Header from "./header/header"; import List from "./list"; import Page from "./pages/page"; import Post from "./posts/post"; import Loading from "./loading"; import Title from "./title"; import PageError from "./page-error"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site */} <Global styles={globalStyles} /> {/* Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> {/* Add the main section */} <Main> <Switch> <Loading when={data.isFetching} /> <List when={data.isArchive} /> <Page when={data.isPage} /> {/* Added Page component */} <Post when={data.isPostType} /> <PageError when={data.isError} /> </Switch> </Main> </> ); }; export default connect(Theme); // styled components
Now we’re importing the <Page / component and have added our <Main /> styled component.
Let’s move on to our custom footer component. You probably know what to do by now: create a new footer.js component file and drop it into the /src/components/footer/ folder. We can add some widgets to our footer that display the sitemap and some sort of “Powered by” blurb:
This is a super simple example. Please note that I have imported a <Widget /> component (line 4, highlighted above) and called the component (line 9, highlighted above). We don’t actually have a <Widget /> component yet, so let’s make that while we’re at it. That can be a widget.js file in the same directory as the footer, /src/components/footer/.
The default header.js component in Mars Theme is very basic with a site title and site description and navigation items underneath. I wanted to refactor the header component with a site logo and title on the left and the nav.js component (top navigation) on the right.
// src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import Nav from "./nav"; import MobileMenu from "./menu"; import logo from "./images/frontity.png" const Header = ({ state }) => { return ( <> <Container> <StyledLink link="/"> {/* Add header logo*/} <Logo src={logo} /> <Title>{state.frontity.title}</Title> </StyledLink> {/*<Description>{state.frontity.description}</Description> */} <Nav /> </Container> <MobileMenu /> </> ); }; // Connect the Header component to get access to the `state` in its `props` export default connect(Header); const Container = styled.div` width: 1000px; // ... `} {/* Logo styled component */} const Logo = styled.img` max-width: 30px; display: inline-block; border-radius: 15px; margin-right: 15px; `; // ...
My refactored header.js component imports a logo image (line 6, highlighted above) and uses in line 14. The nav.js component shown below is basically the same, only with some minor styling modifications.
Adding the <Global> style component
We have already covered the <Global> component and how it’s used for site-wide CSS. There are only a few global styles in the default Mars Theme root component, and I wanted to add more.
I did that with a separate globalStyles file at /src/components/styles/globalStyles.js — similar to Frontity’s Twenty Twenty theme — and added root variables, a CSS reset, and common site-wide element styles, found in the GitHub repo.
Implementing fluid typography
Even though it’s not really in scope, I really wanted to use fluid typography in my custom theme as part of my overall learning journey. So, I added it to the global styles.
It was a fun working with the clamp() function because it meant I could set a range of sizes without any media queries at all!
Adding webfonts to the theme
I also wanted to use a different webfont in my theme. Importing webfonts in CSS using @font-face is covered here on CSS-Tricks. Frontity’s Twenty Twenty Theme uses it, so that’s a good place to reference as well.
We can use the fonts with either with a <link>in the HTML head or with @import in CSS. But Chris covered how to use @font-face with Google Fonts, which allows us to optimize the number of HTTP requests we make since we can download the fonts to our own server.
Those fonts point to a /fonts folder that doesn’t exist. So, let’s make one there and make sure all of the correct font files are in it so the fonts load properly.
Importing globalStyles and @face-font components to the root <Theme /> component
Let’s open our theme root component, /src/components.index.js, and add our globalStyles.js and font-face.js components in there. As shown below, we should import both components into index.js and call the components later.
// src/components/index.js // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site, like body or a's. * Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> <FontFace /> {/* Add the header of the site. */} // ... export default connect(Theme); {/* delete original globalStyles css component */} // ...
Finally, we should remove mars-theme globalStyles component from index.js. Now our new fonts are applied throughout our project.
Styling pages and posts
Our posts and pages are pretty much styled already, except for some Gutenberg block contents, like buttons, quotes, etc.
To style our post entry meta data, let’s add icons for the author, date, categories, and tags. Frontity’s port of the WordPress Twenty Nineteen theme uses SVG icons and components for author.js, categories.js, posted-on.js and tags.js components, which we can totally copy and use in our own project. I literally copied the top-level entry-meta folder and everything in it from the frontity-twentynineteen theme and added it all to the /components/posts/ project folder.
Next we should update our src/components/list/list-item.js component so we can use the new assets:
// src/components/list/list-item.js import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media"; // import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on"; const Item = ({ state, item }) => { return ( <article> <div> {/* If the post has an author, we render a clickable author text. */} <EntryMeta> <Author authorId={item.author} /> {"| "} <PostedOn post={item} /> </EntryMeta> </div> <Link link={item.link}> <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} /> </Link> // ... </article> ); }; // Connect the Item to gain access to `state` as a prop export default connect(Item);
With these styles in place, our archive page entry meta looks good with icons displayed before entry-meta taxonomy (authors, posted-on).
Here we will modify archives taxonomy page styling with more descriptive header. Let’s update list.js component of our /src/components/list/list.js as shown below.
// src/components/list/list.js import React from "react"; import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination"; const List = ({ state }) => { // Get the data of the current list. const data = state.source.get(state.router.link); return ( <Container className="entry-content"> {/* If the list is a taxonomy, we render a title. */} {data.isAuthor ? ( <Header> Author Archives:{" "} <PageDescription> {decode(state.source.author[data.id].name)} </PageDescription> </Header> ) : null} {/* If the list is a taxonomy or category, we render a title. */} {data.isTaxonomy || data.isCategory ? ( <Header> {data.taxonomy.charAt(0).toUpperCase() + data.taxonomy.slice(1)}{" "} Archives:{" "} <PageDescription> {decode(state.source[data.taxonomy][data.id].name)} </PageDescription> </Header> ) : null} // ... <Pagination /> </Container> ); }; export default connect(List); const PageDescription = styled.span` font-weight: bold; font-family: var(--body-family); color: var(--color-text); `; // ...
In the example above, we wrapped taxonomy.id data with PageDesctiption styled component applied some styling rules.
The post pagination in the default Mars Theme is very basic with almost no styling. Let’s borrow from the Frontity Twenty Nineteen theme again and add the pagination component and styling from the theme by copying the pagination.js component file in its entirety, and paste it to /src/components/list/pagination.js in our theme.
I added some minor CSS and it works perfectly in our project.
To customize the actual individual posts and pages, let’s make bold header title that’s centered and displays the entry meta:
// src/components/posts/post.js // ... // Import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on"; import Categories from "../entry-meta/categories"; import Tags from "../entry-meta/tags"; const Post = ({ state, actions, libraries }) => { // ... // Load the post, but only if the data is ready. return data.isReady ? ( <Container className="main"> <div> <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} /> {/* Hide author and date on pages */} {data.isPost && ( <EntryMeta> <Author authorId={post.author} /> <PostedOn post={post} /> </EntryMeta> )} </div> {/* Look at the settings to see if we should include the featured image */} {state.theme.featured.showOnPost && ( <FeaturedMedia id={post.featured_media} /> )} {data.isAttachment ? ( <div dangerouslySetInnerHTML={{ __html: post.description.rendered }} /> ) : ( <Content> <Html2React html={post.content.rendered} /> {/* Add footer meta-entry */} <EntryFooter> <Categories cats={post.categories} /> <Tags tags={post.tags} /> </EntryFooter> </Content> )} </Container> ) : null; }; export default connect(Post); // ...
Adding Gutenberg block styles
WordPress uses a separate stylesheet for blocks in the Block Editor. Right now, that stylesheet isn’t being used but it would be great if we could get some base styles in there that we use for the various block content we add to pages and posts.
That .wp-block-buttons class is declared in the WordPress blocks stylesheet that we aren’t using… yet.
The WordPress Block Editor uses two styling files: style.css and theme.css. Let’s copy these directly from Frontity’s port of the Twenty Twenty theme because that’s how they implemented the WordPress styles. We can place those inside a /styles/gutenberg/ folder.
“Gutenberg” is the codename that was given to the WordPress Block Editor when it was in development. It’s sometimes still referred to that way.
Let’s add the above two style files to our theme root component, /src/components/index.js, just like we did earlier for globalStyles:
// src/components/index.js import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css"
Here’s our updated <Theme /> root component:
// src/components/index.js // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles"; // Add Gutenberg styles import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css" /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site, like body or a's. * Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> <Global styles={css(gutenbergStyle)} /> <Global styles={css(gutenbergTheme)} /> <FontFace /> {/* Add the header of the site. */} // ... export default connect(Theme); {/* Delete original globalStyles css component */} // ...
We could go about overriding styles many different ways. I went with a simple route. For example, to overriding button styles — .wp-block-buttons — in the styled-component for pages and posts.
We can write override any other block styles the same way. In Frontity’s Twenty Nineteen theme, the entire stylesheet from the WordPress version of the theme is added tothe Frontity version to replicate the exact same appearance. Frontity’s Twenty Twenty port uses only a select few of the styles in the WordPress Twenty Twenty themes, but as inline styles.
Additional styling resources
All the resources we covered in this section on styling are available in the GitHub repository. If you wish to expand my @frontity/labre-theme project further, here are the resources that I gathered.
Comments: The native WordPress functionality for comments are described in this guide.
Infinity Scroll Hooks: This Frontity demo project demonstrates how to use the Infinite Scroll Hooks available in the @frontity/hooks package. Here is a YouTube video that covers it.
Yoast SEO: This is a super popular WordPress plugin and I’m sure many of you would want to use it in Frontity as well. Follow this @frontity/package documentation which automatically gets and renders all of the tags exposed in the REST API by the plugin.
Section 6: Resources and credit
There are ample resources to learn and customize your Frontity project. While preparing this post, I have referred to the following resources extensively. Please refer to original posts for more detailed information.
Frontity documentation and articles
Step-by-step tutorial (Frontity): This is the perfect place to start if you’re new to Frontity, or even if you’ve previously used Frontity and want to level up.
Conceptial guides (Frontity): These guides helps solve some of the common challenges that come up when working with dynamic server-side rendering in React apps connected to WordPress.
Frontity API reference (Frontity). This contains detailed information about Frontity CLI, packages, plugins and themes. Once you’ve mastered the basics of working with Frontity, this is where you’re likely to spend most of your time when working on projects.”
Frontity example repo (Frontity): This is a collection of Frontity projects that demonstrate how Frontity is used in the wild.
Connecting Gutenberg and Frontity (Mario Santos, Frontity product manager) This post is based on Mario’s talk at the 2020 JavaScript for WordPress Conference and has an accompanying video.
Frontity case studies
Moving to Frontity: Diariomotor Case Study (Reyes Martinez): Learn how Frontity helped drive the evolution of Diariomotor, reducing development time and putting them on the path to better performance.
Migrating Aleteia to Frontity (Reyes Martinez). Aleteia is the leading website for Catholic news. Frontity allowed them to move to a modern front-end stack in just a couple of months.
Introducing AWSM F1 Theme for Frontity (Venuraj Varma). Awsm Innovations rebuilt their website with Frontity to boost web performance and deliver a great user experience.
How to Fetch the WordPress Menus in Frontity (Michael Burridge). In this video, Michael explains how to dynamically fetch WordPress menu-items using the WordPress WP-REST-API V2 Menus plugin.
Connecting Gutenberg and Frontity: A Case Study (Mario Santos). In this talk video, Frontity product manager Mario explains how the official Frontity website was rebuilt with both the WordPress Block Editor and Frontity, while highlighting all the challenges and lessons learned along the way.
Frontity has a vibrant and engaging community forum for asking questions or getting help regarding your Frontity project.
Wrapping up and personal thoughts
If you can’t already tell from this post or the others I’ve written, I have a huge passion for headless WordPress sites. As I wrote in a previous article, I came across Frontity through when Chris posted this article. I have been experimenting with it for over six months, choosing to take a deep drive into Frontity and the building blocks used in its default Mars Theme. I must admit that it’s a fascinating software framework and I’ve had an enjoyable learning experience. I may even use this sort of setup for my own personal site!
Here are a few key takeaways from my experience working with Frontity so far:
It’s beginner-friendly and low maintenance: One of the things that impressed me most with Frontity is how relatively easy it is to jump into, even as a beginner. It installs with a couple of commands and takes care of all the setup and configuration for connecting to WordPress via the REST API—something I would have struggled with if left to my own devices.
It works with experimental block themes. In my very limited testing, Frontity’s framework works as expected with experimental block themes, just as it does with classic WordPress themes, like Twenty Twenty. I tested with the Quadrat theme that supports the experimental stuff the Gutenberg team is working on.
Hosting is good, but maybe too expensive: As Chris wrote, Frontity is “a perfect match for Vercel.” However, the current Jamstack pricing model that includes Vercel is unattractive for many ordinary WordPress users.
Frontity’s documentation is good, but could be better: The Frontity team recently reorganized Frontity documentation into tutorials, guides and an API reference. However, in my opinion it’s still confusing for those just getting into the framework.
Because I enjoyed this project so much, I am currently doing a theme project from scratch. Even in WordPress, I learned best by getting my hands dirty building WordPress themes from scratch.
While I am still doing my Gatsby and Frontity side projects, I have not lost my sight from the ongoing WordPress block editor and block-based theme development. At the time of writing, there are already sixteen block-based themes in the WordPress theme directory. I have just started exploring and understanding experimental block themes, which might be another interesting learning project.
After this project, my thoughts about Gatsby, Frontity and the concept of headless sites are still evolving. That’s only because it’s tough to make a fair comparison of when a lot of the tooling is actively in development and changing all the time. There are even experimental themes, that are much lighter and different structural markups than the current PHP-based classic themes, which might be a subject for yet another time.
Please share your experience and thoughts if you have been using Frontity in your projects. As always, I enjoy reading any comments and feedback!
Astro is a brand new framework for building websites. To me, the big thing is that it allows you to build a site like you’re using a JavaScript framework (and you are), but the output is a zero-JavaScript static site. You can opt-in to client-side JavaScript as needed, and there are clever options for doing so. Notably, the learning curve is somewhat flattened by the fact that it supports componentry you may already know: React/Preact (JSX), Svelte, Vue, or web components.
Table stakes
Starting a new project is as easy as it should be:
npm init astro npm install npm start
There is a helpful little process and output:
As expected (like you would get with Next or Nuxt or any other site builder kind of project) you get a dev server at a local port you can pop right up:
From here, I consider the table stakes to be CSS injection / Hot Module Reloading. No worries there:
A static site generator with honest-to-god real actual components
This is such a wonderful thing to me. I really like the idea of static site generators—I think they make a lot of sense in a lot of situations. Sending HTML over-the-wire is just a good move for resiliency, CDN-efficiency, SEO, accessibility, you name it. But in the past a lot of the options were either:
A JavaScript powered static site generator, that does generate a “static” site, but also ships a JavaScript bundle (e.g. Next or Gatsby)
A static site generator that is more focused on HTML and has its own templating/formats that aren’t JavaScript components (e.g. Eleventy or Jekyll)
I know there are exceptions, but this covers the vast majority of the site generator market.
But I want both!
I want to craft sites from JavaScript-components, because the syntax and tooling around them is just better than any other component system we have right now.
I want static output that is actually zero-JavaScript (unless I manually opt-in to things).
Astro also has it’s own format (.astro) and it’s also very compelling because:
It’s obviously a first-class citizen of how Astro works
It’s comfortably JSX-like…
…except better because it does stuff like makes the <head> work automatically
Styled scoping works out of the box, through a normal <style> tag
“Fenced” JavaScript runs during build. Let’s look at that next.
Astro files
I mentioned some of the cool parts about the .astro syntax right above. At a higher level, I just like how they look. So little boilerplate! Just gets right to it.
--- import SomeComponent from "../components/SomeComponent"; // This runs in Node, so you look at your command line to see it. console.log("Hi."); // Example: <SomeComponent greeting="(Optional) Hello" name="Required Name" /> const { greeting = 'Hello', name } = Astro.props; const items = ["Dog", "Cat", "Platipus"]; --- <!-- JSX-like, but also more pleasantly HTML like, like this comment --> <div class="module"> <h1>{greeting}, {name}!</h1> <ul> {items.map((item) => ( <li>{item}</li> ))} </ul> </div> <SomeComponent regular="props" /> <style> /* Scoped! */ .module { padding: 1rem; } </style>
The “fences” (---) at the top is where the initial JavaScriptin’ goes. That’s where I yank in the props for this component if it needs any (they can be typed if you like that), do imports/exports, and set up data for the template below.
What feels a little funky, but is in-line with the Astro vibe, is that this is essentially Node JavaScript. It runs in the build process. So that console.log() statement I don’t see in my browser console, I see it in my command line.
pages-style routing
It’s tempting to say Next.js popularized this, but really the concept is as old as file systems. Think of how a classic Apache server works. If you have a file system like:
index.html /about/ index.html
In a browser, you can visit http://website.com/about and that will render that index.html page under the /about folder. That’s what the routing is like here. By virtue of me having:
/pages/ index.astro about.astro
I’ll have a homepage as well as an /about/ page on my site. That’s just a refreshingly nice way to deal with routing—as opposed to needing to build your own routing with component-ry all to itself.
If you want to do that thing where all the content of your site lives in Markdown files right in the repo, that’s a first-class citizen.
I think this is super common for stuff like blogs and documentation, especially as those are already popular targets for static site generators. And in these early days, I think we’re going to see a lot of Astro sites along those lines while people wait to see if it’s ready for bigger undertakings.
One way to use Markdown is to make Pages in Markdown straight away. The Markdown will also have “fences” (Frontmatter) where you chuck what layout you want to use (best to use an .astro file) and pass in data if you need to. Then the entire content of the Markdown file will flow into the <slot />. Pretty darn slick:
Another incredibly satisfying way to use Markdown in Astro is using the built-in <Markdown /> component. Import it and use it:
--- import { Markdown } from 'astro/components'; --- <main> <Markdown> # Hello world! - Do thing - Another thing in my *cool list* </Markdown> <div>Outside Markdown</div> </main>
You can also go snag some Markdown from elsewhere in your project and barf that into a component. That leads into fetching data, so let’s look at that next.
I suppose it’s kind of weird how Astro supports all these different frameworks out of the box.
I’ve overheard some pushback that Astro is inefficient at the npm install level since you have to bring down a bunch of stuff you likely won’t need or use. I’ve overheard some pushback on the idea that mixing-matching JavaScript frameworks is a terrible idea.
I agree it’s weird-feeling, but I’m not particularly worried about non-user-facing things. When things are happening only during the build process and all the user ever gets is HTML, use whatever feels good! If you ultimately do load the components-based frameworks to do on-page interactive things, surely it makes sense to limit it to one. And since you’re getting so much at build time, maybe it makes sense to use something designed for super light on-rendered-page interactivity.
Fetching data rules
We were just talking about Markdown so let’s close the loop there. You can “fetch” data internally in Astro by using fetchContent. Look how straightforward it is:
I fetch it the raw Markdown, then I could use the HTML it returns if I want, or slap it into a <Markdown /> component if that makes sense for whatever reason:
But I don’t have to fetch internal data only. I’m a fan of Eleventy. During an Eleventy build, you can certainly go fetch data from an outside source, but I’d argue it’s a little finnicky. You fetch the data with code in a separate JavaScript file, pulling in your own network library, then processing and returning the data to use elsewhere in a template. Like this. In Astro, that fetching can happen right alongside the component where you need it.
Check out this real-world-ish example where I yank in data from right here from CSS-Tricks and display it as cards.
Check it out, I can build a page from CSS-Tricks data just that easily:
What’s fascinating about that is that the data happens:
in Node, not client-side, and
during the build process.
So, in order to keep a website like this updated, I’d have to run the build/deploy process regularly.
Styling
Let’s say you want to use Sass to style your site. With many site generators, they punt on this, as a philosophy. Like saying “nah, we don’t want to be opinionated here, you style however you want to”. And I get that, it might be a strength as sometimes frameworks that are too opinionated lose people. But to me, it often feels unfortunate as now I’m on my own to wire up some style-processing build processes (e.g. Gulp) that I really just don’t want to deal with.
With Astro, the philosophy seems to be to support a wide swath of popular styling techniques out of the box right away.
Just import "./style.css"; vanilla stylesheets
Use a <style> block anywhere in .astro files and the CSS will be scoped to that component…
… which is like CSS modules, but that’s only needed if you go for a .jsx file, and if you do, it’s supported.
The styling capabilities of .svelte and .vue files work as expected.
Sass is built in, just put <style lang="scss"> on the styling blocks wherever.
<MyComponent /> will render an HTML-only version of MyComponent (default)
<MyComponent:load /> will render MyComponent on page load
<MyComponent:idle /> will use requestIdleCallback() to render MyComponent as soon as main thread is free
<MyComponent:visible /> will use an IntersectionObserver to render MyComponent when the element enters the viewport
That’s some fancy dancing. HTML by default, and you opt-in to running your components client-side (JavaScript) only when you specifically want to, and even then, under efficient conditions.
I put a little Vue-based counter (from their examples) onto my demo site and used the :visible modifier to see it work. Check it out:
The Vue stuff only loads when it needs to. Like the blog post says:
Of course, sometimes client-side JavaScript is inevitable. Image carousels, shopping carts, and auto-complete search bars are just a few examples of things that require some JavaScript to run in the browser. This is where Astro really shines: When a component needs some JavaScript, Astro only loads that one component (and any dependencies). The rest of your site continues to exist as static, lightweight HTML.
The Discord is poppin’
I should point out that Astro is super duper new. As I write, they don’t even have real documentation up. It might feel a bit early to be using a framework with docs that only exist as a README. They are working on it though! I’ve seen previews of it because I happen to be in the Discord.
I think they are very smart to have a public Discord as it means there is a hot-n-fast feedback loop for them to improve the framework. I’ve found that being in it is super useful.
I believe they are hoping that Astro grows up into much more than a framework, but a complete platform, where Astro is just the open-source core. You can hear Fred talk with Jason about that on a Learn with Jason episode.
I’m a WordPress user and, if you’re anything like me, you always have two tabs open when you edit a post: one with the new fancy pants block editor, aka Gutenberg, and another with a preview of the post so you know it won’t look wonky on the front end.
It’s no surprise that a WordPress theme’s styles only affect the front end of your website. The back end posy editor generally looks nothing like the front end result. We’re used to it. But what if I said it’s totally possible for the WordPress editor nearly mirror the front end appearance?
All it takes is a custom stylesheet.
Mind. Blown. Right? Well, maybe it’s not that mind blowing, but it may save you some time if nothing else. 🙂
WordPress gives us a hint of what’s possible here. Fire up the default Twenty Twenty theme that’s packaged with WordPress, fire up the editor, and it sports some light styling.
This whole thing consists of two pretty basic changes:
A few lines of PHP in your theme’s functions.php file that tell the editor you wish to load a custom stylesheet for editor styles
Said custom stylesheet
Right then, enough pre-waffle! Let’s get on with making the WordPress editor look like the front end, shall we?
Step 1: Crack open the functions.php file
OK I was lying, just a little more waffling. If you’re using a WordPress theme that you don’t develop yourself, it’s probably best that you setup achild theme before making any changes to your main theme. </pre-waffle>
Fire up your favorite text editor and open up the theme’s functions.php file that’s usually located in the root of the theme folder. Let’s drop in the following lines at the end of the file:
// Gutenberg custom stylesheet add_theme_support('editor-styles'); add_editor_style( 'style-editor.css' ); // make sure path reflects where the file is located
What this little snippet of code does is tell WordPress to add support for a custom stylesheet to be used with Gutenberg, then points to where that stylesheet (that we’re calling editor-style.css) is located. WordPress has solid documentation for the add_theme_support function if you want to dig into it a little more.
Step 2: CSS tricks (see what I did there?!)
Now we’re getting right into our wheelhouse: writing CSS!
We’ve added editor-styles support to our theme, so the next thing to do is to add the CSS goodness to the stylesheet we defined in functions.php so our styles correctly load up in Gutenberg.
There are thousands of WordPress themes out there, so I couldn’t possibly write a stylesheet that makes the editor exactly like each one. Instead, I will show you an example based off of the theme I use on my website. This should give you an idea of how to build the stylesheet for your site. I’ll also include a template at the end, which should get you started.
OK let’s create a new file called style-editor.css and place it in the root directory of the theme (or again, the child theme if you’re customizing a third-party theme).
Writing CSS for the block editor isn’t quite as simple as using standard CSS elements. For example, if we were to use the following in our editor stylesheet it wouldn’t apply the text size to <h2> elements in the post.
h2 { font-size: 1.75em; }
Instead of elements, our stylesheet needs to target Block Editor blocks. This way, we know the formatting should be as accurate as possible. That means <h2> elements needs to be scoped to the .rich-text.block-editor-rich-text__editable class to style things up.
It just takes a little peek at DevTools to find a class we can latch onto.
I just so happened to make a baseline CSS file that styles common block editor elements following this pattern. Feel free to snag it over at GitHub and swap out the styles so they complement your theme.
I could go on building the stylesheet here, but I think the template gives you an idea of what you need to populate within your own stylesheet. A good starting point is to go through the stylesheet for your front-end and copy the elements from there, but you will likely need to change some of the element classes so that they apply to the Block Editor window.
If in doubt, play around with elements in your browser’s DevTools to work out what classes apply to which elements. The template linked above should capture most of the elements though.
The results
First of all, let’s take a look at what the WordPress editor looks like without a custom stylesheet:
The block editor sports a clean, stark UI in its default appearance. It’s pulling in Noto Serif from Google Fonts but everything else is pretty bare bones.
Let’s compare that to the front end of my test site:
Things are pretty different, right? Here we still have a simple design, but I’m using gradients all over, to the max! There’s also a custom font, button styling, and a blockquote. Even the containers aren’t exactly square edges.
Love it or hate it, I think you will agree this is a big departure from the default Gutenberg editor UI. See why I have to have a separate tab open to preview my posts?
Now let’s load up our custom styles and check things out:
Well would you look at that! The editor UI now looks pretty much exactly the same as the front end of my website. The content width, fonts, colors and various elements are all the same as the front end. I even have the fancy background against the post title!
Ipso facto — no more previews in another tab. Cool, huh?
Making the WordPress editor look like your front end is a nice convenience. When I’m editing a post, flipping between tabs to see what the posts looks like on the front end ruins my mojo, so I prefer not to do it.
These couple of quick steps should be able to do the same for you, too!
I’m excited to share some of the newer features in Chrome DevTools with you. There’s a brief introduction below, and then we’ll cover many of the new DevTools features. We’ll also look at what’s happening in some other browsers. I keep up with this stuff, as I create Dev Tips, the largest collection of DevTools tips you’ll find online!
It’s a good idea to find out what’s changed in DevTools because it’s constantly evolving and new features are specifically designed to help and improve our development and debugging experience.
Let’s jump into the latest and greatest. While the public stable version of Chrome does have most of these features, I’m using Chrome Canary as I like to stay on the bleeding edge.
Lighthouse
Lighthouse is an open source tool for auditing web pages, typically around performance, SEO, accessibility and such. For a while now, Lighthouse has been bundled as part of DevTools meaning you can find it in a panel named… Lighthouse!
Well done, Mr. Coyier. 🏆
I really like Lighthouse because it’s one of easiest parts of DevTools to use. Click “Generate report” and you immediately get human-readable notes for your webpage, such as:
Document uses legible font sizes 100% legible text
Or:
Avoid an excessive DOM size (1,189 elements)
Almost every single audit links to developer documentation that explains how the audit may fail, and what you can do to improve it.
The best way to get started with Lighthouse is to run audits on your own websites:
Open up DevTools and navigate to the Lighthouse panel when you are on one of your sites
Select the items you want to audit (Best practices is a good starting point)
Click on any passed/failed audits to investigate the findings
Even though Lighthouse has been part of DevTools for a while now (since 2017!), it still deserves a significant mention because of the user-facing features it continues to ship, such as:
An audit that checks that anchor elements resolve to their URLs (Fun fact: I worked on this!)
An audit that checks whether the Largest Contentful Paint metic is fast enough
This is a subtle and, in some ways, very small feature, but it can have profound effects on how we treat web accessibility.
Here’s how it works. When you use Inspect Element — what is arguably the most common use of DevTools — you now get a tooltip with additional information on accessibility.
Accessibility is baked right in!
The reason I say this can have a profound impact is because DevTools has had accessibility features for quite some time now, but how many of us actually use them? Including this information on a commonly used feature like Inspect Element will gives it a lot more visibility and makes it a lot more accessible.
The tooltip includes:
the contrast ratio of the text (how well, or how poorly, does the foreground text contrast with the background color)
Exactly as it says on the tin, you can use Chrome DevTools to emulate vision impairments. For example, we can view a site through the lens of blurred vision.
That’s a challenge to read!
How can you do this in DevTools? Like this:
Open DevTools (right click and “Inspect” or Cmd + Shift + C).
Open the DevTools Command menu (Cmd + Shift + P on Mac, Ctrl + Shift + P on Windows).
Select Show Rendering in the Command menu.
Select a deficiency in the Rendering pane.
We used blurred vision as an example, but DevTools has other options, including: protanopia, deuteranopia, tritanopia, and achromatopsia.
Like with any tool of this nature, it’s designed to be a complement to our (hopefully) existing accessibility skills. In other words, it’s not instructional, but rather, influential on the designs and user experiences we create.
Here are a couple of extra resources on low vision accessibility and emulation:
The Performance Panel in DevTools can sometimes look like a confusing mish-mash of shapes and colors.
This update to it is great because it does a better job surfacing meaningful performance metrics.
What we want to look at are those extra timing rectangles shown in the “Timings” in the Performance Panel recording. This highlights:
DOMContentLoaded: The event which triggers when the initial HTML loads
First Paint: When the browser first paints pixels to the screen
First Contentful Paint: The point at which the browser draws content from the DOM which indicates to the user that content is loading
Onload: When the page and all of its resources have finished loading
Largest Contentful Paint: The largest image or text element, which is rendered in the viewport
As a bonus, if you find the Largest Contentful Paint event in a Performance Panel recording, you can click on it to get additional information.
Nice work, CSS-Tricks! The Largest Contentful Paint happens early on in the page load.
While there is a lot of golden information here, the “Related Node” is potentially the most useful item because it specifies exactly which element contributed to the LCP event.
To try this feature out:
Open up DevTools and navigate to the Performance panel
Click “Start profiling and reload page”
Observe the timing metrics in the Timings section of a recording
Click the individual metrics to see what additional information you get
Monitor performance
If you want to quickly get started using DevTools to analyze performance and you’ve already tried Lighthouse, then I recommend the Performance Monitor feature. This is sort of like having WebPageTest.org right at your fingertips with things like CPU usage.
Here’s how to access it:
Open DevTools
Open up the Command menu (Cmd + Shift + P on Mac, Ctrl + Shift + P on Windows)
Select “Show performance monitor” from the Command menu
Interact and navigate around the website
Observe the results
The Performance Monitor can give you interesting metrics, however, unlike Lighthouse, it’s for you to figure out how to interpret them and take action. No suggestions are provided. It’s up to you to study that CPU usage chart and ask whether something like 90% is an acceptable level for your site (it probably isn’t).
The Performance Monitor has an interactive legend, where you can toggle metrics on and off, such as:
CPU usage
JS heap size
DOM Nodes
JS event listeners
Documents
Document Frames
Layouts / sec
Style recalcs / sec
CSS overview and local overrides
CSS-Tricks has already covered these features, so go and check them out!
CSS Overview: A handy DevTools panel that gives a bunch of interesting stats on the CSS your page is using
Local Overrides: A powerful feature that lets you override production websites with your local resources, so you can easily preview changes
So, what about DevTool in other browsers?
I’m sure you noticed that I’ve been using Chrome throughout this article. It’s the browser I use personally. That said, it’s worth considering that:
Firefox DevTools is looking pretty great right now
With Microsoft Edge extending from Chromium, it too will benefit from these DevTools features
In other words, keep an eye out because this is a quickly evolving space!
Conclusion
We covered a lot in a short amount of space!
Lighthouse: A panel that provides tips and suggestions for performance, accessibility, SEO and best practices.
Inspect Element: An enhancement to the Inspect Element feature that provides accessibility information to the Inspect Element tooltip
Emulate vision deficiencies: A feature in the Rendering Pane to view a page through the lens of low vision.
Performance Panel Timings: Additional metrics in the Performance panel recording, showing user-orientated stats, like Largest Contentful Paint
Performance Monitor – A real-time visualization of performance metrics for the current website, such as CPU usage and DOM size
Please check out my mailing list, Dev Tips, if you want to stay keep up with the latest updates and get over 200 web development tips! I also have a premium video course over at ModernDevTools.com. And, I tend to post loads of bonus web development resources on Twitter.
Oh hey! A brand new property that affects how a box is sized! That’s a big deal. There are lots of ways already to make an aspect-ratio sized box (and I’d say this custom properties based solution is the best), but none of them are particularly intuitive and certainly not as straightforward as declaring a single property.
So, with the impending arrival of aspect-ratio (MDN, and not to be confused with the media query version), I thought I’d take a look at how it works and try to wrap my mind around it.
Shout out to Una where I first saw this. Boy howdy did it strike interest in folks:
Just dropping aspect-ratio on an element alone will calculate a height based on the auto width.
Without setting a width, an element will still have a natural auto width. So the height can be calculated from the aspect ratio and the rendered width.
.el { aspect-ratio: 16 / 9; }
If the content breaks out of the aspect ratio, the element will still expand.
The aspect ratio becomes ignored in that situation, which is actually nice. Better to avoid potential data loss. If you prefer it doesn’t do this, you can always use the padding hack style.
If the element has either a height or width, the other is calculated from the aspect ratio.
So aspect-ratio is basically a way of seeing the other direction when you only have one (demo).
If the element has both a height and width, aspect-ratio is ignored.
The combination of an explicit height and width is “stronger” than the aspect ratio.
Factoring in min-* and max-*
There is always a little tension between width, min-width, and max-width (or the height versions). One of them always “wins.” It’s generally pretty intuitive.
If you set width: 100px; and min-width: 200px; then min-width will win. So, min-width is either ignored because you’re already over it, or wins. Same deal with max-width: if you set width: 100px; and max-width: 50px; then max-width will win. So, max-width is either ignored because you’re already under it, or wins.
It looks like that general intuitiveness carries on here: the min-* and max-* properties will either win or are irrelevant. And if they win, they break the aspect-ratio.
.el { aspect-ratio: 1 / 4; height: 500px; /* Ignored, because width is calculated to be 125px */ /* min-width: 100px; */ /* Wins, making the aspect ratio 1 / 2 */ /* min-width: 250px; */ }
With value functions
Aspect ratios are always most useful in fluid situations, or anytime you essentially don’t know one of the dimensions ahead of time. But even when you don’t know, you’re often putting constraints on things. Say 50% wide is cool, but you only want it to shrink as far as 200px. You might do width: max(50%, 200px);. Or constrain on both sides with clamp(200px, 50%, 400px);.
But say you run into that minimum 200px, and then apply a min-width of 300px? The min-width wins. It’s still intuitive, but it gets brain-bending because of how many properties, functions, and values can be involved.
Maybe it’s helpful to think of aspect-ratio as the weakest way to size an element?
It will never beat any other sizing information out, but it will always do its sizing if there is no other information available for that dimension.
I recently had an opportunity to try the new Vue Composition API in a real project to check where it might be useful and how we could use it in the future.
Until now, when we were creating a new component we were using Options API. That API forced us to separate the component’s code by options, meaning that we needed to have all reactive data in one place (data), all computed properties in one place (computed), all methods in one place (methods), and so on.
As it is handy and readable for smaller components, it becomes painful when the component gets more complicated and deals with multiple functionalities. Usually, logic related to one specific functionality contains some reactive data, computed property, a method or a few of them; sometimes it also involves using component lifecycle hooks. That makes you constantly jump between different options in the code when working on a single logical concern.
The other issue that you may have encountered when working with Vue is how to extract a common logic that can be reused by multiple components. Vue already has few options to do that, but all of them have their own drawbacks (e.g. mixins, and scoped-slots).
The Composition API brings a new way of creating component, separating code and extracting reusable pieces of code.
Let’s start with code composition within a component.
Code composition
Imagine you have a main component that sets up few things for your whole Vue app (like layout in Nuxt). It deals with the following things:
setting locale
checking if the user is still authenticated and redirects them if not
preventing the user from reloading the app too many times
tracking user activity and reacting when the user is inactive for specific period of time
listening on an event using EventBus (or window object event)
Those are just a few things the component can do. You can probably imagine a more complex component, but this will serve the purpose of this example. For the sake of readability, I am just using names of the props without the actual implementation.
This is how the component would look like using Options API:
As you can see, each option contains parts from all functionalities. There is no clear separation between them and that makes the code hard to read, especially if you are not the person who wrote it and you are looking at it for the first time. It is very hard to find which method is used by which functionality.
Let’s look at it again but identify the logical concerns as comments. Those would be:
Now imagine you need to make a change in one functionality (e.g. activity tracking logic). Not only do you need to know which elements are related to that logic, but even when you know, you still need to jump up and down between different component options.
Let’s use the Composition API to separate the code by logical concerns. To do that we create a single function for each logic related to a specific functionality. This is what we call a composition function.
// Activity tracking logic function useActivityTracker() { const userActivityTimeout = ref(null) const lastUserActivityAt = ref(null) function activateActivityTracker() {...} function deactivateActivityTracker() {...} function resetActivityTimeout() {...} function userActivityThrottler() {...} onBeforeMount(() => { activateActivityTracker() resetActivityTimeout() }) onUnmounted(() => { deactivateActivityTracker() clearTimeout(userActivityTimeout.value) }) }
// Reload blocking logic function useReloadBlocker(context) { const reloadCount = ref(null) function blockReload() {...} function setReloadCount() {...} onMounted(() => { setReloadCount() blockReload() }) }
// Locale logic function useLocale(context) { async function loadLocaleAsync(selectedLocale) {...} function setI18nLocale(locale) {...} watch(() => { const locale = ... loadLocaleAsync(locale) }) // No need for a 'created' hook, all logic that runs in setup function is placed between beforeCreate and created hooks const initialLocale = localStorage.getItem('locale') loadLocaleAsync(initialLocale) }
// Event bus listener registration import EventBus from '@/event-bus' function useEventBusListener(eventName, handler) { onMounted(() => EventBus.$ on(eventName, handler)) onUnmounted(() => EventBus.$ off(eventName, handler)) }
As you can see, we can declare reactive data (ref / reactive), computed props, methods (plain functions), watchers (watch) and lifecycle hooks (onMounted / onUnmounted). Basically everything you normally use in a component.
We have two options when it comes to where to keep the code. We can leave it inside the component or extract it into a separate file. Since the Composition API is not officially there yet, there are no best practices or rules on how to deal with it. The way I see it, if the logic is tightly coupled to a specific component (i.e. it won’t be reused anywhere else), and it can’t live without the component itself, I suggest leaving it within the component. On the flip side, if it is general functionality that will likely be reused, I suggest extracting it to a separate file. However, if we want to keep it in a separate file, we need to remember to export the function from the file and import it in our component.
This is how our component will look like using newly created composition functions:
<template> <div id="app"> </div> </template> <script> export default { name: 'App', setup(props, context) { useEventBusListener(MY_EVENT, handleMyEvent) useActivityTracker() useReloadBlocker(context) useLocale(context) const isAuthenticated = computed(() => ...) watch(() => { if (!isAuthenticated) {...} }) function handleMyEvent() {...}, function useLocale() {...} function useActivityTracker() {...} function useEventBusListener() {...} function useReloadBlocker() {...} } } </script>
This gives us a single function for each logical concern. If we want to use any specific concern, we need to call the related composition function in the new setup function.
Imagine again that you need to make some change in activity tracking logic. Everything related to that functionality lives in the useActivityTracker function. Now you instantly know where to look and jump to the right place to see all the related pieces of code. Beautiful!
Extracting reusable pieces of code
In our case, the Event Bus listener registration looks like a piece of code we can use in any component that listens to events on Event Bus.
As mentioned before, we can keep the logic related to a specific functionality in a separate file. Let’s move our Event Bus listener setup into a separate file.
That’s it! We can now use that in any component we need.
Wrapping up
There is an ongoing discussion about the Composition API. This post has no intention to promote any side of the discussion. It is more about showing when it might be useful and in what cases it brings additional value.
I think it is always easier to understand the concept on a real life example like above. There are more use cases and, the more you use the new API, the more patterns you will see. This post is merely a few basic patterns to get your started.
Let’s go again through the presented use cases and see where the Composition API can be useful:
General features that can live on its own without tight coupling with any specific component
All logic related to a specific feature in one file
Keep it in @/composables/*.js and import it in components
Examples: Activity Tracker, Reload Blocker, and Locale
Reusable features that are used in multiple components
All logic related to a specific feature in one file
Keep it in @/composables/*.js and import in components
Examples: Event Bus listener registration, window event registration, common animation logic, common library usage
Code organization within component
All logic related to a specific feature in one function
Keep the code in a composition function within the component
The code related to the same logical concern is in the same place (i.e. there’s no need to jump between data, computed, methods, lifecycle hooks, etc.)
Remember: This is all a work-in-progress!
The Vue Composition API is currently at work in progress stage and is subject to future changes. Nothing mentioned in the examples above is sure, and both syntax and use cases may change. It is intended to be shipped with Vue version 3.0. In the meantime, you can check out view-use-web for a collection of composition functions that are expected to be included in Vue 3 but can be used with the Composition API in Vue 2.
People say JAMstack sites are fast — let’s find out why by looking at real performance metrics! We’ll cover common metrics, like Time to First Byte (TTFB) among others, then compare data across a wide section of sites to see how different ways to slice those sites up compare.
First, I’d like to present a small analysis to provide some background. According to the HTTPArchive metrics report on page loading, users wait an average of 6.7 seconds to see primary content.
First Contentful Paint (FCP) – measures the point at which text or graphics are first rendered to the screen.
The FCP distribution for the 10th, 50th and 90th percentile values as reported on August 1, 2019.
If we are talking about engagement with a page (Time to Interactive), users wait even longer. The average time to interactive is 9.3 seconds.
Time to Interactive (TTI) – a time when user can interact with a page without delay.
TTI distribution for the 10th, 50th and 90th percentile values as reported on August 1, 2019.
State of the real user web performance
The data above is from lab monitoring and doesn’t fully represent real user experience. Real users data based taken from the Chrome User Experience Report (CrUX) shows an even wider picture.
I’ll use data aggregated from users who use mobile devices. Specifically, we will use metrics like:
TTFB represents the time browser waits to receive first bytes of the response from server. TTFB takes from 200ms to 1 second for users around the world. It’s a pretty long time to receive the first chunks of the page.
TTFB mobile speed distribution (CrUX, July 2019)
First Contentful Paint
FCP happens after 2.5 seconds for 23% of page views around the world.
FCP mobile speed distribution (CrUX, July 2019)
First Input Delay
FID metrics show how fast web pages respond to user input (e.g. click, scroll, etc.).
CrUX doesn’t have TTI data due to different restrictions, but has FID, which is even better can reflect page interactivity. Over 75% of mobile user experiences have input delay for 50ms and users didn’t experience any jank.
FID mobile speed distribution (CrUX, July 2019)
You can use the queries below and play with them on this site.
#standardSQL SELECT REGEXP_REPLACE(yyyymm, '(d{4})(d{2})', '1_2_01') AS date, UNIX_DATE(CAST(REGEXP_REPLACE(yyyymm, '(d{4})(d{2})', '1-2-01') AS DATE)) * 1000 * 60 * 60 * 24 AS timestamp, IF(device = 'desktop', 'desktop', 'mobile') AS client, ROUND(SUM(fast_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS fastFCP, ROUND(SUM(avg_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS avgFCP, ROUND(SUM(slow_fcp) * 100 / (SUM(fast_fcp) + SUM(avg_fcp) + SUM(slow_fcp)), 2) AS slowFCP, ROUND(SUM(fast_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS fastFID, ROUND(SUM(avg_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS avgFID, ROUND(SUM(slow_fid) * 100 / (SUM(fast_fid) + SUM(avg_fid) + SUM(slow_fid)), 2) AS slowFID FROM `chrome-ux-report.materialized.device_summary` WHERE yyyymm = '201907' GROUP BY date, timestamp, client ORDER BY date DESC, client
State of Content Management Systems (CMS) performance
CMSs should have become our saviors, helping us build faster sites. But looking at the data, that is not the case. The current state of CMS performance around the world is not so great.
TTFB mobile speed distribution comparison between all web and CMS (CrUX, July 2019)Data from July 2019
E-Commerce websites, a good example of sites that are typically built on a CMS, have really bad stats for page views:
~40% – 1second for TTFB
~30% – more than 1.5 second for FCP
~12% – lag for page interaction.
I faced clients who requested support of IE10-IE11 because the traffic from those users represented 1%, which equalled millions of dollars in revenue. Please, calculate your losses in case 1% of users leave immediately and never came back because of bad performance. If users aren’t happy, business will be unhappy, too.
To get more details about how web performance correlates with revenue, check out WPO Stats. It’s a list of case studies from real companies and their success after improving performance.
With JAMstack, developers do as little rendering on the client as possible, instead using server infrastructure for most things. Not to mention, most JAMstack workflows are great at handling deployments, and helping with scalability, among other benefits. Content is stored statically on a static file hosts and provided to the users via CDN.
I had two years of experience working with one of the popular CMSs for e-commerce and we had a lot of problems with deployments, performance, scalability. The team would spend days and fixing them. It’s not what customers want. These are the sorts of big issues JAMstack solves.
Looking at the CrUX data, JAMstack sites performance looks really solid. The following values are based on sites served by Netlify and GitHub. There is some discussion on the HTTPArchive forum where you can participate to make data more accurate.
Here are the results for TTFB:
TTFB mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)Data from July 2019
#standardSQL SELECT COUNT(DISTINCT origin) AS n, ROUND(SUM(IF(ttfb.start < 200, ttfb.density, 0)) / SUM(ttfb.density), 4) AS fastTTFB, ROUND(SUM(IF(ttfb.start >= 200 AND ttfb.start < 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS avgTTFB, ROUND(SUM(IF(ttfb.start >= 1000, ttfb.density, 0)) / SUM(ttfb.density), 4) AS slowTTFB FROM `chrome-ux-report.all.201907`, UNNEST(experimental.time_to_first_byte.histogram.bin) AS ttfb JOIN (SELECT url, REGEXP_EXTRACT(LOWER(CONCAT(respOtherHeaders, resp_x_powered_by, resp_via, resp_server)), '(netlify|x-github-request)') AS platform FROM `httparchive.summary_requests.2019_07_01_mobile`) ON CONCAT(origin, '/') = url WHERE platform IS NOT NULL ORDER BY n DESC
Here’s how FCP shook out:
FCP mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)
Now let’s look at FID:
FID mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)Data from July 2019
#standardSQL SELECT COUNT(DISTINCT origin) AS n, ROUND(SUM(IF(fcp.start < 1000, fcp.density, 0)) / SUM(fcp.density), 4) AS fastFCP, ROUND(SUM(IF(fcp.start >= 1000 AND fcp.start < 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS avgFCP, ROUND(SUM(IF(fcp.start >= 2500, fcp.density, 0)) / SUM(fcp.density), 4) AS slowFCP, ROUND(SUM(IF(fid.start < 50, fid.density, 0)) / SUM(fid.density), 4) AS fastFID, ROUND(SUM(IF(fid.start >= 50 AND fid.start < 250, fid.density, 0)) / SUM(fid.density), 4) AS avgFID, ROUND(SUM(IF(fid.start >= 250, fid.density, 0)) / SUM(fid.density), 4) AS slowFID FROM `chrome-ux-report.all.201907`, UNNEST(first_contentful_paint.histogram.bin) AS fcp, UNNEST(experimental.first_input_delay.histogram.bin) AS fid JOIN (SELECT url, REGEXP_EXTRACT(LOWER(CONCAT(respOtherHeaders, resp_x_powered_by, resp_via, resp_server)), '(netlify|x-github-request)') AS platform FROM `httparchive.summary_requests.2019_07_01_mobile`) ON CONCAT(origin, '/') = url WHERE platform IS NOT NULL ORDER BY n DESC
The numbers show the performance of JAMstack sites is the best. The numbers are pretty much the same for mobile and desktop which is even more amazing!
Some highlights from engineering leaders
Let me show you a couple of examples from some prominent folks in the industry:
Out of 468 million requests in the @HTTPArchive, 48% were not served from a CDN. I've visualized where they were served from below. Many of them were requests to 3rd parties. The client requesting them was in Redwood City, CA. Latency matters. #WebPerfpic.twitter.com/0F7nFa1QgM
JAMstack sites are generally CDN-hosted and mitigate TTFB. Since the file hosting is handled by infrastructures like Amazon Web Services or similar, all sites performance can be improved in one fix.
One more real investigation says that it is better to deliver static HTML for better FCP.
Which has a better First Meaningful Paint time?
① a raw 8.5MB HTML file with the full text of every single one of my 27,506 tweets ② a client rendered React site with exactly one tweet on it
(Spoiler: @____lighthouse reports 8.5MB of HTML wins by about 200ms)
Here’s a comparison for all results shown above together:
Mobile speed distribution comparison between all web, CMS and JAMstack sites (CrUX, July 2019)
JAMstack brings better performance to the web by statically serving pages with CDNs. This is important because a fast back-end that takes a long time to reach users will be slow, and likewise, a slow back-end that is quick to reach users will also be slow.
JAMstack hasn’t won the perf race yet, because the number of sites built with it not so huge as for example for CMS, but the intention to win it is really great.
Adding these metrics to a performance budget can be one way make sure you are building good performance into your workflow. Something like:
TTFB: 200ms
FCP: 1s
FID: 50ms
Spend it wisely 🙂
Editor’s note: Artem Denysov is from Stackbit, which is a service that helps tremendously with spinning up JAMstack sites and more upcoming tooling to smooth out some of the workflow edges with JAMstack sites and content. Artem told me he’d like to thank Rick Viscomi, Rob Austin, and Aleksey Kulikov for their help in reviewing the article.
Dan Mall is judging the Communication Arts Interactive 2020 awards. These types of things are usually a celebration of flashy, short-lived, one-off designs. Those things are awesome, but Dan has more in mind:
I’d love to award work that demonstrates creative use of the highest level of color contrast ratios and works well on assistive devices. I’d love to award work that’s still useful when JavaScript fails. I’d love to award work that shows smart thinking and strategy in addition to flawless execution and art direction. I’d love to award work that serves business and user needs.