5 Content Marketing Ideas for November 2021

Comparisons, top 10 lists, customer stories, an old mystery, and Thanksgiving leftovers are all topics that could influence your company’s content marketing in November 2021.

Content marketing is the act of creating, publishing, and promoting content with the express purpose of attracting, engaging, and retaining customers.

What follows are five content marketing ideas your business could try in November 2021.

1. ‘Versus’ Articles

In content marketing lingo, a “versus” page or article compares two or more options to help a reader make an informed buying decision.

A versus page might pit your product against a competitor’s, or it could compare two techniques, options, or ideas.

Picture of a pair of brown men's shoes from the Joseph Cheaney & Sons website.Picture of a pair of brown men's shoes from the Joseph Cheaney & Sons website.

Comparison articles such as this one contrasting Oxfords and Brogues are helpful to readers investigating a product. Image: Joseph Cheaney & Sons.

These pages can be both interesting to read and good for organic search rankings.

Here are a few example titles.

  • A power tool retailer: “Wood Studs vs. Metal: Which Is Better for Residential Construction?”
  • A supplement brand: “Soy Protein vs. Whey: Which Is Better for Women?”
  • A shoe store: “Oxfords vs. Brogues, a Guide”

That last one, “Oxfords vs. Brogues, a Guide,” is an article published on the Joseph Cheaney & Sons website.

For your November 2021 content marketing, create versus pages that represent topics for last-minute holiday shoppers.

2. Top 10

Similar in concept to a versus piece is a listing of the best products or services, such as the top 10.

These articles compare or list similar products or methods, and often identify the best overall, the best value, or best for some purpose.

A 2016 article from men’s fashion retailer Mr Porter, for example, listed “Ten Of The World’s Most Masculine Dogs.”

Image from the Mr Porter article showing a male and a dogImage from the Mr Porter article showing a male and a dog

Mr Porter has its top 10 list of dogs.

A direct-to-consumer kitchen supply brand could borrow an idea from Esquire magazine and publish “22 of the Best Gins to Drink in 2021.”

This article listed the top gins, described the research, and offered category winners. Ableforth’s Bathtub Gin, according to the Esquire editors, was best for a dry martini, while Monkey 47 Schwarzwald Dry Gin was the best gift.

The top 10 articles you publish in November could seek to address common holiday searches.

3. Customer Stories

There is never a bad time to feature customers in content. This is especially true for aspirational brands with consumers who purchase, in part, to be included in a group or associated with an idea, such as shoppers who choose products or brands for environmental, social, or corporate governance issues.

Customer stories can take multiple forms, including profile articles, podcast interviews, or short video documentaries.

The content of these stories should focus on the customer’s experience and its relationship to the products your business sells.

Photo of firefighters in a forest settingPhoto of firefighters in a forest setting

Customer stories help prospects connect to a brand.

A hiking boot brand could feature a customer who is a wildland firefighter. The western United States, for example, has been experiencing difficult fire seasons, putting stress on firefighters who often work consecutive 16-hour days.

This story could describe the customer’s experiences on the job and then associate his experiences with the hiking boots he wears.

4. D.B. Cooper Day: November 24

On November 24, 1971, an average-looking fellow calling himself Dan Cooper paid cash for a ticket on Northwest Orient Airlines flight 305 from Portland International Airport to Seattle.

Shortly after the plane took off at 2:50 p.m. Pacific Standard Time, Cooper ordered a bourbon and soda and passed a handwritten note to Florence Schaffner, a flight attendant seated nearby. The note said Cooper had a bomb.

Photo of two drawings of what D.B. Cooper may have looked likePhoto of two drawings of what D.B. Cooper may have looked like

These FBI drawings of D.B. Cooper have not necessarily helped solve the mystery.

Although he was calm, polite, and even considerate, Cooper clearly stated his intention. He would blow up everyone on board and destroy the plane unless he received $200,000 in cash, four parachutes, and a ride south toward Mexico City.

Without alerting the other passengers, the demands were passed to the pilots in the cockpit, then to the tower, and eventually to authorities and Northwest Orient’s president, Donald Nyrop. Nyrop approved the payment, and authorities gathered the four parachutes from a local skydiving school.

After circling the Seattle airport for a few hours and experiencing refueling trouble and complex flight instructions, the Boeing 727-100 headed south with a small crew, the booty, and Cooper.

At about 8:15 p.m., Cooper lept from the plane with a parachute and the cash, never to be seen again. The hijacking is the only officially unsolved case of air piracy in history, despite relatively recent claims that Cooper has been identified.

The mystery has had a considerable following over the past 49 years and has been the subject of numerous books, movies, and documentaries. The fictional Marvel character Loki recently claimed he was D.B. Cooper during an episode of the Disney+ series that bears the character’s name!

The D.B. Cooper mystery is intriguing. Recounting it could make sense for a D2C brand selling travel or skydiving gear.

5. National Leftovers Day: November 26

Photo of cooked TurkeyPhoto of cooked Turkey

Turkey left over from Thanksgiving can be used in many recipes.

In the United States, National Leftovers Day immediately follows Thanksgiving and focuses our attention on the turkey and trimmings remaining after the feast.

From the content marketing perspective, National Leftovers Day begs for recipes.

Local Testing A Serverless API (API Gateway And Lambda)

This article is for anyone struggling with testing cloud services locally, and specifically for people wanting to locally test an API that uses API Gateway and Lambda, with the Serverless framework, on AWS. I was once in desperate need of this knowledge, and my co-worker Joseph Jaffe helped me put this solution into place.

A very popular and quick API solution is using Serverless along with API Gateway and Lambda. If you have never done this before, you can read more about it here. If you already have experience, and are looking for creative ways to locally test, then keep reading. Bonus if you like 🌮🌮!

The Problem

When setting up an API using the Serverless framework, there are important choices to make. Some are extremely important: they can make your life much easier as you build your API, or cause you huge headaches in the future. For instance, when I first started my API, I was setting up each endpoint in its own Serverless project folder. Thus each endpoint had unique URLs. This is bad practice for an API solution that needs one base URL with multiple service points. We changed the solution to have all endpoints in the same Serverless project, and that allowed us to use one extra handy aspect of Lambda — layers. Lambda layers are a way to share code across API endpoints, reducing the amount of repeated code across the API project. You can read more about Lambda Layers here, but I digress. Let’s get back to the problem: local testing!

When you create a new API endpoint, you must deploy the entire API to include the new resource in API Gateway and get the URL for it. Once you have done that, you can deploy individual resources.

Deploying for Serverless takes time. Lots of time. For instance, see these average deploy times for one of our last projects:

  • Deploying a single endpoint: ~7 seconds 🙂
  • Deploying full API (~12 resources): ~24 seconds 😔
  • Deploying Layers (2 layers): ~32 seconds 💀

While these times don’t appear too bad at first glance, imagine trying to quickly and iteratively test out changes in your API. Having each deploy over 1 minute long is a huge time-hog and will kill your momentum. Stretch this over weeks of development and you will see why we all need a solution to test Lambda functions locally.

Our Solution

In order to quickly flush out the obvious errors, we require local testing. In order to test locally, we found that serverless invoke local allows us to do this in the simplest manner. This not only allows us to run Lambda scripts locally, but we can also add breakpoints using Debug Mode in Visual Studio Code. If you have never played with it, Debug Mode in VSC is really helpful.

The most important aspect of local testing is instant feedback. No more waiting around for layers, functions or entire APIs to deploy! Tons of time saved during the initial build and your (patience as well as your) Program Director will love you for it!


The last Serverless API project we worked on stored all the data in JSON files hosted on AWS S3 buckets. You may be reading the data from a database, so you need to make sure you can still access the data while running locally. One way would be to set up the database locally on your machine. Ultimately, every project is unique and requires you to think creatively for a solution that meets your needs.

Environment Variables

In order for us to know if we are running locally or not, we created an environment variable to pass in through our local invocation. The variable is named LOCAL_DEV and is used to check if we should be loading the data from S3 or from a local file system folder, like so:

const data = process.env.LOCAL_DEV === "true" ? require(`./data/tacos.json`) : //handle loading/setting the data as you regularly would

Note above that the boolean value of true is in quotes. Environment variables always come through as strings, so be ready to handle this fact of life.

We have a snapshot of the data stored on S3 on our local computers, so when we are in local development mode, we use that local data in order to run and test the code locally.

Additionally, if you are using layers in Lambda, you will need to point directly to the code as opposed to referring to it by name, at the top of your Lambda file:

const apiCommon = process.env.LOCAL_DEV === "true"
? require("../layers/apicommon/nodejs/node_modules/apicommon/index")
: require("apicommon");

Local Invocation

Once you have all code in place to allow the Lambda function to run successfully locally, then you can try invoking the function. Here is an example invocation of an endpoint called tacos (🌮🌮) that gets all tacos from a food API. Because I ❤️ 🌮🌮. Code for this example can found on Github.

This is copied and pasted from a command shortcut I defined in my package.json file. That command requires you to put literal \ markers in front of all quotes. Here is that command from package.json in its entirety:

"scripts": { "local-tacos": "serverless invoke local --function tacos --data '{ \"queryStringParameters\": {\"type\": \"breakfast\", \"filling1\": \"egg\", \"filling2\": \"bacon\", \"filling3\": \"cheese\", \"tortilla\": \"flour\", \"salsa\": \"Salsa Doña\"}}' -e LOCAL_DEV=true > output.json"

Ok, now let’s look at this command and what each part does. I am removing all of the literal markers for easier readability.

serverless invoke local --function tacos --data '{ "queryStringParameters": {"type": "breakfast", "filling1": "egg", "filling2": "bacon", "filling3": "cheese", "tortilla": "flour", "salsa": "Salsa Doña"}}' -e LOCAL_DEV=true > output.json

First, the base part:

serverless invoke local --function tacos

The item above says to locally invoke the API endpoint “tacos” (local 🌮🌮 are the best, right?) which gets a set of tacos filtered by whatever query string parameters you send it. Next, let’s look at the second part.

--data '{ "queryStringParameters": {"type": "breakfast", "filling1": "egg", "filling2": "bacon", "filling3": "cheese", "tortilla": "flour", "salsa": "Salsa Doña"}}'

Here is where we can define whatever query string parameters we are passing into the API endpoint. For this example, we pass into the API endpoint all the attributes that describe the taco(s) we are looking for. In our case, we are looking for egg, bacon and cheese tacos on a flour tortilla with Salsa Doña.

Note: Any guess as to where the taco described above (with Salsa Doña) can be found in Austin, Texas, USA? If you know, please respond in the comments!

If you need to test a bunch of parameters, you could save them out in a testing/query.json file(s) so you could do something like:

yarn local-taco query-success yarn local-taco query-fail

This could be a good start for an API testing environment!

The third part of the call is for defining any environment variables.

-e LOCAL_DEV=true

This tells our Lambda code very clearly we are running this function locally and to make sure and prepare for that by pulling all resources in locally as well.

Last, I pipe the results into a JSON file.

> output.json

From here I can easily verify if the results are correct or if an error was thrown.


That sums it up! If you didn’t see the link earlier, I have a sample project written up that you can try on your own, using the Serverless framework, the AWS services API Gateway, and Lambda.

Also, if you have ideas on how to make this solution better, or other alternative solutions, I would love to hear about your feedback/tips/experiences in the comments below.

Further Reading On Smashing Magazine

🎧 Bonus: Smashing Podcast Episode 22 With Chris Coyier: What Is Serverless? (moderated by Drew McLellan)

6 TikTok Tools for Shopping

TikTok is the latest social media platform to focus on ecommerce. TikTok unveiled its shopping platform at its inaugural World event, with new tools to help brands and merchants sell to TikTok’s users. The release is a culmination of recent efforts to integrate shopping into TikTok.

Social commerce on TikTok looks promising. The hashtag #TikTokMadeMeBuyIt has over 5 billion views. TikTok claims its users are 1.7-times more likely to have purchased the products they discover through the app as compared to other social platforms.

Here is a list of new shopping tools from TikTok. For goods and services that appeal to a younger demographic, TikTok may fulfill the long-heralded promise of social commerce.

TikTok Shopping

TikTok Shopping is a comprehensive service — from product upload to fulfillment. TikTok has been testing this service in Indonesia, and now it’s available in the U.K. as well.

Later this year, TikTok will release a Shopping API, allowing merchants to integrate their product catalogs directly into TikTok.

Merchants can also connect third-party ecommerce platforms and product catalogs through TikTok partnerships. Shopify began a partnership earlier this year. TikTok has announced new partnerships with Square, Ecwid, and PrestaShop. Additionally, Wix, Shopline, OpenCart, and Base will be available soon.

Screenshot a of Square page, reading "Start selling on TikTok"Screenshot a of Square page, reading "Start selling on TikTok"

Square: Start Selling on TikTok

Product Links

Product Links spotlight products within a video so that followers can see the product in action and immediately make a purchase. Merchants can display multiple product links in any video containing those items. Viewers can explore featured products without leaving the app.

Collection Ads

Collection Ads allow businesses to include product cards in their In-Feed Ads. Viewers that select an item in a card see a fast-loading instant gallery page, where they can browse and purchase more items. This fast-loading catalog experience can feature seasonal deals, share limited-time offers, and showcase items.

Mobile screen shot of example Collection AdsMobile screen shot of example Collection Ads

Collection Ads

Dynamic Showcase Ads

Dynamic Showcase Ads let businesses automatically promote thousands of products targeted at user interests and activity, such as viewing a product or adding to a cart.

TikTok’s new partnership with Productsup lets brands integrate their product catalogs to create Dynamic Showcase Ads. TikTok’s marketing partner Shakr will provide templates and services to help brands run Dynamic Showcase Ads campaigns with high-volume product catalogs.

Example of four Dynamic Showcase AdsExample of four Dynamic Showcase Ads

Dynamic Showcase Ads

Live Shopping

Live Shopping allows merchants to seamlessly integrate their products from TikTok Shopping into a live stream. Connect with an audience in real-time, engage followers, and help users to discover new products.

Lead Generation

TikTok’s Lead Generation service lives within In-Feed Ads, making it easy for users interested in a brand or product to share their contact information securely.

TikTok’s new partnerships with Zapier and Leadsbridge allow leads generated through TikTok to flow directly into a customer management platform in real-time, bypassing the need to download or manage data manually.

Screen capture of mobile "TikTok for Business" pageScreen capture of mobile "TikTok for Business" page

Lead Generation

How To Build An Expandable Accessible Gallery

One of the use cases for using CSS Grid is to display a gallery of images, but a gallery on its own may not be that exciting. We could, for example, add a click effect to enlarge the image without affecting the grid to make it a little bit more fun. And of course, as to not exclude anybody from enjoying this feature, we should make it accessible, too.

In this article, I’ll explain how to build an accessible expandable gallery with a few tips and tricks along the way. Here’s how the final result looks like:

See the Pen How to build accessible expandable gallery by Silvestar Bistrović.


First, we are going to set the HTML structure. Of course, we could always do it in various ways, but let us use a list of images wrapped in buttons.

<ul class="js-favs"> <li> <button> <img src="/path/to/image" alt="" /> </button> </li> ...

Now, to make the gallery accessible, we need to make some adjustments:

  • Add the descriptive alt attribute to every image to help visually impaired people understand what is in the image;
  • Use the aria-expanded attribute which informs assistive technologies if the image is expanded or not;
  • Include role="list" to make sure assistive technologies announce the list because some screen readers might remove the list announcement.

“It’s not just using list-style: none, but any CSS that would remove the bullet or number indicators of a list’s items will also remove the semantics.”

“Fixing” Lists, Scott O’Hara

Finally, let’s add a paragraph with helpful text on how to use the gallery, and wrap the whole code in a landmark (in this case, the main element).

<main> <p>Use ESC to close larger picture.</p> <ul class="js-favs" role=”list”> <li> <button aria-expanded="false"> <img src="/path/to/image" alt="Description of the image." /> </button> </li> ... </ul>

For the simplicity of the demo, I decided to use images wrapped with the aria-expanded attribute. A better solution might be to add only image tags and then use JavaScript to wrap these images in a button with the aria-expanded attribute. This may be considered as progressive enhancement since the expanding effect wouldn’t work without JavaScript anyway.


To define the grid layout, we could use CSS Grid. We’ll use auto-fit so that items can fit into the available space, but restrict themselves from shrinking under a certain width. This means that we’ll see a different number of items on different viewports without writing too many media queries.

:root { --gap: 4px;
} ul { display: grid; grid-template-columns: repeat(1, 1fr); grid-gap: var(--gap);
} @media screen and (min-width: 640px) { ul { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

To preserve the correct aspect ratio of the image, we could use the aspect-ratio property. To reset the button style, we could add the all: initial declaration. We should also hide the overflow of the button.

To make the image fit right into the button, we’ll use object-fit: cover declaration and set both width and height to 100%:

button { all: initial; display: block; width: 100%; aspect-ratio: 2/1; overflow: hidden; cursor: pointer;
} img { height: 100%; width: 100%; object-fit: cover;

The expanding effect is done with the scale transformation. The transition is enabled by default, but if the user does not prefer transitions and animations, we can use the prefers-reduced-motion media query and set the transition-duration property to 0s.

:root { --duration-shrink: .5s; --duration-expand: .25s; --no-duration: 0s;
} li { transition-property: transform, opacity; transition-timing-function: ease-in-out; transition-duration: var(--duration-expand);
} li.is-zoomed { transition-duration: var(--duration-shrink);
} @media (prefers-reduced-motion) { li, li.is-zoomed { transition-duration: var(--no-duration); }

The JavaScript


Before we make the element expandable, we need to prepare and calculate a few things.

First, we’ll need to get the duration of the transition by reading the CSS Custom Property --duration-on.

let timeout = 0 // Get the transition timeout from CSS
const getTimeouts = () => { const durationOn = parseFloat(getComputedStyle(document.documentElement) .getPropertyValue('--duration-on')); timeout = parseFloat(durationOn) * 1000

Next, we’ll set the data attributes for the later calculation:

  • the gap of the grid elements;
  • the width of a single element;
  • the number of items per row.

The first two are pretty straightforward. We could get the values from the computed CSS style.

To find the number of columns, we should iterate through each tile and compare the top position of each element. Once the top position changes, the item is in the new row, which gets us the number of items.

// Set data attributes for calculations
const setDataAttrs = ($elems, $parent) => { // Get the top offset of the first element let top = getTop($elems[0]) // Set grid gap from CSS const gridColumnGap = parseFloat(getComputedStyle(document.documentElement) .getPropertyValue('--gap')) $parent.setAttribute('data-gap', gridColumnGap) // Set grid item width from CSS const eStyle = getComputedStyle($elems[0]) $parent.setAttribute('data-width', eStyle.width) // Iterate through grid items for (let i = 0; i < $elems.length; i++) { const t = getTop($elems[i]) // Check when top offset changes if (t != top) { // Set the number of columns and break stop the loop $parent.setAttribute('data-cols', i) break; } }

Expanding Direction

To achieve the expandable effect, we should make some checks and calculations first. First, we should check if the item is in the last row and at the end of the row. If the item is in the last row, it should expand to the top. That means it should have the transform-origin property set to the bottom value.

Important: If the element should expand to one direction, its transform-origin property should be set to an “opposite” value. Note that vertical and horizontal values should be combined.

// Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => { // Get data attributes from parent const cols = parseInt($parent.getAttribute('data-cols')) const width = parseFloat($parent.getAttribute('data-width')) const gap = parseFloat($parent.getAttribute('data-gap')) // Calculate the number of rows const rows = Math.ceil(lengthOfElems / cols) - 1 // Calculate if the item is in the last row const isLastRow = i + 1 > rows * cols // Set default transform direction to top (expand down) let transformOrigin = 'top' if (isLastRow) { // If the item is in the last row, set transform direction to bottom (expand up) transformOrigin = 'bottom' } // Calculate if the item is the most right const isRight = (i + 1) % cols !== 0 if (isRight) { // If the item is the most right, set transform direction to left (expand right) transformOrigin += ' left' } else { // If the item is the most right, set transform direction to right (expand left) transformOrigin += ' right' } $elem.style.transformOrigin = transformOrigin

Expanding Effect

To enlarge the image without affecting the grid, we could use CSS transforms. In particular, we should use the scale transformation. I decided to make the image double in size, i.e. the factor is the ratio of the double width of the element plus grid-gap.

// Calculate the scale coefficient
const scale = (width * 2 + gap) / width // Set item CSS transform
$elem.style.transform = `scale(${scale})`

Keyboard Support

Users who navigate sites by using a keyboard should be able to use the gallery. Going through the list works by default when using key Tab. Emulating the click works by default by pressing the Enter key while the item is focused. To enhance the default behavior, we should add support for Esc and the arrow keys.

Once we expand the item, pressing Esc should revert it to its standard size. We could do it by checking the code of the pressed key. The same goes for arrow keys, but the action is different. When pressing arrow keys, we want to get the previous or next sibling and then emulate the click on that element.

// Set sibling as an active item
const activateSibling = ($sibling) => { // Find anchor const $siblingButton = $sibling.querySelector('button') // Unset global active element $activeElem = false // Focus and click on current $siblingButton.focus() $siblingButton.click()
} // Set keyboard events
const setKeyboardEvents = () => { document.addEventListener('keydown', (e) => { // Take action only if global active element exists if ($activeElem) { // If key is “escape”, emulate the click on the global active element if (e.code === 'Escape') { $activeElem.click() } // If key is “left arrow”, activate the previous sibling if (e.code === 'ArrowLeft') { const $previousSibling = $activeElem.parentNode.previousElementSibling if($previousSibling) { activateSibling($previousSibling) } } // If key is “right arrow”, activate the next sibling if (e.code === 'ArrowRight') { const $nextSibling = $activeElem.parentNode.nextElementSibling if($nextSibling) { activateSibling($nextSibling) } } } })


To make the gallery element expanded, we should deactivate all other elements first. Then, if we click on the expanded element, it should revert to the standard size.

let $activeElem = false // Deactivate grid items
const deactiveElems = ($elems, $parent, $currentElem, $button) => { // Unset parent class $parent.classList.remove('is-zoomed') for (let i = 0; i < $elems.length; i++) { // Unset item class $elems[i].classList.remove('is-zoomed') // Unset item CSS transform $elems[i].style.transform = 'none' // Skip the rest if the item is the current item if ($elems[i] === $currentElem) { continue } // Unset item aria expanded if element exists if($button) { $button.setAttribute('aria-expanded', false) } }
} // Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => { ... // Reset all elements deactiveElems($elems, $parent, $elem, $button) if ($activeElem) { $activeElem = false return } $activeElem = $button ...
} // Set click events on anchors
const setClicks = ($elems, $parent) => { $elems.forEach(($elem, i) => { // Find anchor const $button = $elem.querySelector('button') $button.addEventListener('click', (e) => { // Set active item on click activateElem($elems, $parent, $elem, $button, $elems.length, i) }) })

Z-index Issues

To prevent issues with z-index and stacking context, we should use the timeout to delay the transform. That is the same timeout that we calculated in the preparation phase.

// Deactivate grid items
const deactiveElems = ($elems, $parent, $currentElem, $button) => { for (let i = 0; i < $elems.length; i++) { ... // After a half of the timeout, reset CSS z-index to avoid overlay issues setTimeout(() => { $elems[i].style.zIndex = 0 }, timeout) }
} // Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => { ... setTimeout(() => { // Set parent class $parent.classList.add('is-zoomed') // Set item class $elem.classList.add('is-zoomed') // Set item CSS transform $elem.style.transform = `scale(${scale})` // Set item aria expanded $button.setAttribute('aria-expanded', true) // Set global active item $activeElem = $button }, timeout)

Viewport Resizing

If the viewport changes the size, we need to recalculate defaults because we defined a fluid grid that allows items to fill the available space and move from row to row.

// Set resize events
const setResizeEvents = ($elems, $parent) => { window.addEventListener('resize', () => { // Set data attributes for calculations setDataAttrs($elems, $parent) // Deactivate grid items deactiveElems($elems, $parent) })

A Word About Accessibility And Credits

I had no problems building this demo except with the accessibility part. I was not sure what to do and which aria attributes to use at first. Even after figuring out which attributes to use, I could not be 100% sure it was right. So the first step was to test everything with a keyboard. That was the easy part. Then I used the VoiceOver application (since I am using a Mac) to test how it works for visually impaired persons. It sounded good enough to me.

However, even after all that testing, I was still not 100% sure. So I decided to ask for help. I am a part of one Slack community for designers and developers (BoagWorld), and I posted a question there. Fortunately, accessibility experts like Todd Libby helped me test the demo on different devices and correct the code. I also asked Manuel Matuzović for help and he helped me clean up the code.

I’m grateful to have the Internet and developer communities where we can all ask for help, get answers from professionals, and solve problems together. That is especially true with sensitive issues like accessibility. Accessibility is hard, and it does not take much to make it wrong. Less is more — at least it was in my case.

And finally, I wanted to share the greatest lesson:

“If you can use a native HTML element [HTML51] or attribute with the semantics and behavior you require already built-in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.”

First Rule of ARIA Use, W3C Working Draft 27 (Sept. 2018)

Further Reading on Smashing Magazine

Ecommerce Product Releases: October 3, 2021

Here is a list of product releases and updates for late September from companies that offer services to online merchants. There are updates on augmented reality tools, conversational commerce, social commerce, small business financing, fraud protection, and buy-now, pay-later.

Got an ecommerce product release? Email releases@practicalecommerce.com.

Ecommerce Product Releases

WPP and Snap launch augmented reality partnership. WPP, the multinational marketing agency, and Snap Inc., the camera company behind Snapchat, have announced a global partnership (“The AR Lab”) to help brands build and deliver augmented reality experiences. With a focus on ecommerce, the partnership combines Snap’s AR technology with WPP’s integrated capabilities across creative, media, commerce, and technology, allowing WPP clients to better connect with their customers on the Snapchat platform and drive meaningful business results through AR.

Home page of WPPHome page of WPP


Quiq launches conversational AI platform for commerce payments and conversational surveys. Quiq, an AI-powered conversational platform that enables businesses to engage with customers, has announced the addition of two new features: payments and surveys. Quiq’s Conversational Commerce Payments enables brands to safely and securely accept payments across all Quiq supported channels, including SMS-text messaging, Apple Business Chat, Google Business Messages, webchat, Instagram, Facebook Messenger, and more. Quiq’s In-Conversation Surveys enable a short survey to be sent immediately following the end of customer interaction, regardless of the channel, for real-time feedback. Quiq has standard support for Stripe and Braintree payments platforms and integrates with any brand’s payment processing platform, through the Quiq Payments API.

GoDaddy Payments launches two point-of-sale devices. GoDaddy has expanded GoDaddy Payments with the launch of two point-of-sale devices: a countertop smart terminal and a mobile card reader. The introduction adds to GoDaddy’s commerce services, giving small businesses the ability to sell, track, and manage transactions in more places. GoDaddy’s POS seamlessly integrates with GoDaddy’s Online Store to unify in-person and online sales in the new Commerce Hub. For GoDaddy’s POS, customers are charged a transaction fee of 2.3% + 0 cents, and online transactions are 2.3% + 30 cents.

Amazon and Lendistry launch Amazon Community Lending program for SMBs. Amazon and Lendistry announced a joint pilot program, Amazon Community Lending, aimed at driving growth opportunities for small and medium-sized businesses selling on Amazon. The program is a new financing option through Amazon Lending. AmazonCommunity Lending will provide U.S.-based Amazon sellers access to short-term loans of up to $100,000. Lendistry is a minority-led community development financial institution that serves urban and rural small businesses in socially and economically distressed communities.

Home page of Amazon Community LendingHome page of Amazon Community Lending

Amazon Community Lending

TikTok launches shopping platform. At its inaugural World event, TikTok released the official TikTok Shopping suite, a commerce service for merchants wherein TikTok manages everything — from product upload to point of purchase, shipping, and fulfillment. The suite includes Product Links, Live Shopping, Collections Ads, Dynamic Showcase Ads, and Lead Generation. Additionally, brands can connect their product catalog to TikTok via third-party platforms, including Shopify, Square, Ecwid, and PrestaShop. Wix, Shopline, OpenCart, and Base will be available soon.

Google adds new ways to shop visually. Google is adding tools for shoppers to browse and find new products and brands in a more visual way. Users can now shop right from Search as it displays a visual feed of products, alongside other helpful information, which can then be filtered. This new experience is powered by Google’s Shopping Graph, a comprehensive, real-time dataset of products, inventory, and merchants with more than 24 billion listings. Also, when users search for products, they can now select the “in stock” filter to see only the nearby stores that have it on their shelves. And starting soon, iOS users will see a new button in the Google app to make all the images on a page searchable through Google. Google is also bringing Lens to Chrome on the desktop. Users can select images, video, and text content on a website with Lens to quickly see search results in the same tab, without leaving the page they’re on.

Cart.com partners with Clearco to deliver funding for ecommerce brands. Cart.com, an ecommerce services provider, has announced a partnership with Clearco, an investment company, to transform the way that ecommerce founders access financing. Through the partnership, Cart.com’s clients will have direct access to Clearco’s capital financing from within their Cart.com dashboard — up to $10 million in capital within 24 hours without giving up equity or control of their company — and then repay the loan from future revenues. Founders can also access up to $1 million in inventory loans. Clearco’s capital products will extend Cart.com’s financing offerings.

Home page of Cart.comHome page of Cart.com


Squarespace releases new selling features. Squarespace has released a series of features to help customers sell online for physical products, digital content, classes, appointments, reservations. and more. One of the products is the Squarespace Video Studio, an app to produce professional-quality videos to engage audiences and help sell ideas, goods, and services. The new features also include tools for Etsy sellers.

Carrot, a plugin that categorizes cart contents, raises $5.5 million. Carrot is a plugin that saves the products put in a cart and automatically categorizes them. The company has closed a $5.5 million seed round with investors that include Kindred Ventures, M13, Abstract, Designer Fund, Combine, Paris Hilton, Scott Belsky, Riverpark, and NextView. Carrot captures what’s put in the cart without any additional buttons or signals, regardless of the retailer. The plugin then automatically categorizes carts based on the retailer itself, but also allows users to create their own folders. The product also tracks pricing changes for items in a cart, giving users the chance to see when something goes on sale.
Mastercard offers new installment plans for buy-now, pay-later. Mastercard’s new buy-now, pay-later service makes it easier for banks to offer installment plans, letting consumers pay for purchases over time. Lenders can approve consumers for an installment loan before purchase or offer the option during checkout. As part of the new program, Mastercard will allow lenders to use its consumer banking data in their underwriting decisions. For merchants, buy-now, pay-later services increase average sales by 45% and reduce the risk that consumers will abandon their shopping carts before they finish checking out online, according to Mastercard.

ClearSale partners with Bold Commerce for checkout protection. ClearSale, a fraud-protection service, has announced a partnership with Bold Commerce, an ecommerce platform. ClearSale can now integrate with Bold Checkout, the company’s customizable checkout framework, to provide global fraud prevention. ClearSale adds advanced statistical fraud analysis to minimize friction during checkout and provides the lowest level of false declines while providing a no-chargeback guarantee.

Home page of ClearSaleHome page of ClearSale


PPE Provider Thrives on Influencers (and Hollywood)

Roman Zrazhevskiy’s company, Mira Safety, sells protective gas masks. It launched in 2018, targeting consumers, medical providers, law enforcement, and military personnel. The masks protect against tear gas, chemicals, and, yes, viruses. Still, reaching prospects was a challenge.

“We did a lot of influencer affiliate marketing,” Zrazhevskiy told me. “We reached out to industry experts and said, ‘We’d love for you to check out our product and write an unbiased review.’”

Fast forward to 2021, and Mira Safety has thrived. Covid-19 created unprecedented demand. But Zrazhevskiy’s influencer marketing program spread the word. A Hollywood movie added even more exposure.

He and I recently discussed his journey. Our entire audio conversation is embedded below. The transcript that follows is edited for length and clarity.

Eric Bandholz: I’m guessing 2020 and 2021 were good for Mira Safety.

Roman Zrazhevskiy: Yes, we had incredible years.

Bandholz: How did people find you?

Zrazhevskiy: It was one step at a time. We started selling on Amazon. We funneled the money we made on Amazon to build a robust website. And we did a lot of influencer affiliate marketing. We reached out to industry experts and said, “We’d love for you to check out our product and write an unbiased review, whatever you feel about the product.” We’re very confident. We stand behind everything we sell.

The most important thing is to have a superior product, know the market, and what else is out there. All of the influencers that received it were like, “Wow, this is great.” We manufacture at a defense contractor, which has been making similar products since the 1920s or so.

Bandholz: How does influencer marketing work for personal protective equipment?

Zrazhevskiy: I went through a process called research hacking, where I would put myself in the shoes of someone researching this product niche, looking for a product like this, and see who’s talking about products like this and who the major players are.

For gas masks, I would search on Google for “best gas masks,” “gas masks for children” — all these keyword variations. I would list on a spreadsheet all of the blogs I found. Then I would personally reach out to each one. I would describe our product and invite them to join our affiliate program.

It’s essential to be transparent. Give them the resources they need to write good content, including quality images and technical specs. Send them a product. We give them access to our affiliate dealer folder that has product-by-product info, 360-degree photos, everything.

Influencer outreach has a cascading effect. When a few big guys feature you, others want to. Now we’re getting cool people reaching out to us, saying, “I love your brand. I love what you guys are doing. I think my audience would be receptive to this. They would love to learn about it. Could you send me something, and I’ll create some content?”

Bandholz: Do you have requirements, such as a minimum number of followers or domain rank?

Zrazhevskiy: Yes. Twenty thousand followers per channel, with engagement. We’re looking for someone strong in at least one channel. It’s rare to be strong in all channels. Our deciding factor is whether they create quality content. We’ll use some of that content and chop it up into ads later on. No one’s ever had a problem with it.

For example, I might take their video and divide it into mini segments of ads on Facebook. It’s been successful for us. We don’t have to produce the content ourselves.

With advertising, you have to keep creating fresh content. If you stop, your ads die. Then Facebook sees that engagement is going down, and they penalize you because they assume the ad is no longer relevant to the audience.

Creating content takes someone working full-time. Targeting is easy. What’s hard is producing the creatives.

Bandholz: Back to the influencers. You’re reaching out one by one. What are your goals?

Zrazhevskiy: I’ll decide each day what to work on. I’ll wake up and decide, for example, to focus on influencer stuff. I’ll do a full day of influencer sprints — research, outreach, sending out a bunch of emails.

I’ll create a series of templates. I customize those templates. And I start reaching out through email. I keep a pipeline of these influencers in HubSpot as a deal. That way I know who I reached out to. I take notes if they respond. My goal is to move it from outreach sent to products sent to review posted.

We work with an influencer agency for the big guys. They have gatekeepers, handlers. With the big guys, you have to pay for videos, and they create the content. There’s no guarantee of performance.

Bandholz: How many of those emails do you send each day?

Zrazhevskiy: Around 20 to 30. It’s all about quality, not quantity. You don’t want to blast through, make mistakes, and write something that’s impersonal or doesn’t read well.

I’m a student of neuro-linguistic programming. I write things in a certain way, and then I test it by the response. I try to craft my messages to entice a reply.

Bandholz: What subject lines get the most response?

Zrazhevskiy: One of my big ones is “Product review with 15% commissions.” I’m telling the influencer what I want and the commission percentage.

Sometimes I negotiate the percentages, but typically it’s 10% to 15%. People who we really want we’ll offer 15%. But roughly 95% of influencers get a 10% commission.

Bandholz: What affiliate software do you recommend?

Zrazhevskiy: For anything outdoor — camping, hiking, fitness — I would go with AvantLink. It’s an affiliate network. AvantLink has tracking technology. They show click rates, earnings per click, everything. They make 3% on every sale.

Some affiliate networks take more, some less. But the most important factor is finding a network that’s strong in your niche. For my niche, it’s AvantLink.

Bandholz: How often do you pay the commissions?

Zrazhevskiy: Every month. AvantLink takes their cut and pays the influencers.

I intended to mention another benefit of affiliate marketing beyond immediate sales. It’s a form of social proof. I put affiliate videos on my product pages.

I give visitors all the research they need on those pages right there and then. I provide them with a Q and A section, which goes deep into common questions. I give them my Instagram feed, which has the product on Instagram in use by customers. I include 360-degree spins. Most importantly, I include my YouTube feed. Some of our product pages have 15 videos from influencers. And, also, I include user-generated reviews.

That strategy has worked for us. We provide so much information that somebody could spend hours on that product page without having to look elsewhere.

Bandholz: Can we talk about your Hollywood win?

Zrazhevskiy: Sure. In May 2019, I received a voicemail from Warner Brothers. I first thought it was a scam. But I called back. I spoke to a lady, a product placement specialist with Warner Brothers. She said they were working on a new film by Christopher Nolan, the director of “Inception.”

She called the new movie “Merry Go Round.” That was the working title. She said Chris likes our mask.

Then we negotiated. It’s imperative to negotiate terms before sending anything, even if it’s an exciting opportunity, especially in Hollywood. You have to have everything in writing. We negotiated the credits. They initially didn’t want to include us in the credits. We insisted on it. We sent them around 300 masks and filters. That’s a lot considering each one retails for $250. But it was a dream come true, a once-in-a-lifetime thing. So we sent them the products. The movie released in 2020. It’s science fiction, called “Tenet.”

Bandholz: It’s such a cool movie.

Zrazhevskiy: For sure. A 10-minute section in the movie featured 300 people in a battle scene wearing our masks. It was shown to hundreds of millions of people worldwide.

Bandholz: The whole premise of the movie was wearing those masks.

Zrazhevskiy: Yes, exactly. It’s a blessing. I feel it was God who shaped that whole opportunity. It was such a humbling, exciting experience. It’s incredible. Since then, we have had three to four other movies, major blockbusters, which will feature our masks.

Bandholz: How can listeners learn more about you and your company and reach out?

Zrazhevskiy: Our website is MiraSafety.com. We’re on Instagram @MIRASafety or #MIRASafety. I’m on LinkedIn.

How To Implement Search Functionality In Your Nuxt App Using Algolia InstantSearch

Giving users the ability to quickly search through and navigate our content easily comes with great benefits. This not only improves the user experience, but also increases user retention and boosts conversion as users can now explore beyond what brought them to our site in the first place.

In this tutorial, we’ll be looking at how to integrate this search functionality into our Nuxt app using Algolia. Algolia is a third-party service that we can integrate into our app and provides us with a set of tools that allow us to create a full search experience in our sites and applications.

We’ll be using Nuxt Content, “Git Based Headless CMS” which allows us to create and manage content using Markdown, XML, JSON files, and so on. We’ll build a Nuxt site with Nuxt Content with a search feature using Algolia InstantSearch, for styling, we’ll use TailwindCSS. This tutorial is aimed at Vue.js devs that are familiar with Nuxt.


To follow along with this tutorial, you’ll need to have the following installed:

  • Node,
  • A text editor, I recommend VS Code with the Vetur extension (for Vue.js syntax features in VS Code),
  • A terminal, you can use VS Code’s integrated terminal or any other of your choice.

You’ll also require a basic understanding of the following in order to follow along smoothly:

Setting Up Our Nuxt App

Nuxt.js is a framework built on Vue, it has many capabilities and features including Server-Side Rendering (SSR).

To install it, open our terminal and run:

npx create-nuxt-app <project-name>

Where <project-name> is the name of our project folder, I’ll be using algolia-nuxt for this project.

Running the command will ask you some questions (name, Nuxt options, UI framework, TypeScript, etc. ). To find out more about all the options, see the Create Nuxt app.

When asked for Nuxt.js modules, make sure to select Content - Git-based headless CMS to install the nuxt/content module along with our Nuxt app.

After selecting all of your options, installation can begin. My selected options look like this:

After successfully installing the Nuxt app, navigate to the directory by running this command:

cd algolia-nuxt

Install Nuxt Content Separately

If you already have Nuxt set up before now, you can install the content module by running the command.

Skip this if you’ve already selected to install the nuxt/content module along with our Nuxt app.

#install nuxt content npm install @nuxt/content

Then you can add it to our modules property inside our nuxt.config file.

//nuxt.config.js export default { modules: ['@nuxt/content']

Install And Setup TailwindCSS

TailwindCSS is a utility first CSS framework that provides us with custom classes we can use to style our app.

We’ll also be using TailwindCSS Typography, which is “a plugin that provides a set of prose classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control (like HTML rendered from Markdown, or pulled from a CMS).”

First, we install @nuxtjs/tailwindcss which is a Nuxt module for TailwindCSS integration, as well as TailwindCSS and its peer-dependencies using npm:

npm install -D @nuxtjs/tailwindcss tailwindcss@latest postcss@latest autoprefixer@latest

Add the @nuxtjs/tailwindcss module to the buildModules section of our nuxt.config.js file:

// nuxt.config.js export default { buildModules: ['@nuxtjs/tailwindcss']

Create Configuration File

Next, generate our tailwind.config.js file:

npx tailwindcss init

This will create a minimal tailwind.config.js file at the root of our project:

//tailwind.config.js module.exports = { purge: [], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [],

Create a tailwind.css file in assets/css/ use the @tailwind directive to inject TailwindCSS’ base, components, and utilities styles:

/*assets/css/tailwind.css*/ @tailwind base;
@tailwind components;
@tailwind utilities;

You can import the CSS file into our components or make it accessible globally by defining the CSS files/modules/libraries you want to set globally (included in every page).

 /* nuxt.config.js*/ // Global CSS: https://go.nuxtjs.dev/config-css css: [ // CSS file in the project '@/assets/css/tailwind.css', ],

Here, we have added the path to our tailwind.css file to the list of global CSS files in our nuxt.config.js.

The @/ tells Nuxt that it’s an absolute path to look for the file from the root directory.

Install TailwindCSS Typography

# Using npm
npm install @tailwindcss/typography

Then add the plugin to our tailwind.config.js file:

// tailwind.config.js
module.exports = { purge: [], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [ require('@tailwindcss/typography'), ],

Configure TailwindCSS To Remove Unused Styles In Production

In our tailwind.config.js file, configure the purge option with the paths to all of our pages and components so TailwindCSS can tree-shake unused styles in production builds:

// tailwind.config.js
module.exports = { purge: [ './components/**/*.{vue,js}', './layouts/**/*.vue', './pages/**/*.vue', './plugins/**/*.{js,ts}', './nuxt.config.{js,ts}', ], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [ require('@tailwindcss/typography'), ],

Since we’ve installed the packages, let’s start our app:

npm run dev

This command starts our Nuxt app in development mode.

Nice 🍻

Creating Our Pages And Articles

Now, let’s create our articles and a blog page to list out our articles. But first, let’s create a site header and navigation component for our site.

Creating A Site Header And Navigation

Navigate to our components/folder, and create a new file siteHeader.vue and enter the following code:

<!-- components/siteHeader.vue --> <template> <header class="fixed top-0 w-full bg-white bg-opacity-90 backdrop-filter backdrop-blur-md"> <div class="wrapper flex items-center justify-between p-4 m-auto max-w-5xl"> <nuxt-link to="/"> <Logo /> </nuxt-link> <nav class="site-nav"> <ul class="links"> <li> <nuxt-link to="/blog">Blog</nuxt-link> </li> </ul> </nav> </div> </header>

Here, in our <header> we have a <Logo /> component wrapped in <nuxt-link> which routes to the home page and another <nuxt-link> that routes to /blog (We’ll create the blog page that we will create later on).

This works without us importing the components and configuring routing ourselves because, by default, Nuxt handles importing components and routing for us.

Also, let’s modify the default <Logo /> component. In components/Logo.vue, replace the content with the following code:

<!-- components/Logo.vue --> <template> <figure class="site-logo text-2xl font-black inline-block"> <h1>Algolia-nuxt</h1> </figure>

We can now add our siteHeader.vue component to our site. In layouts/default.vue, add <site-header /> just above the <Nuxt /> component.

<!-- layouts/default.vue --> <template> <div> <site-header /> <Nuxt /> </div>
</template> ...

The <Nuxt /> component renders the current Nuxt page depending on the route.

Creating Our First Article

In content/, which is a folder created automatically for the nuxt/content module, create a new folder articles/ and then a new file in the folder first-blog-post.md. Here is the file for our first article in markdown format. Enter the following code:

<!-- content/articles/first-blog-post.md --> --- title: My first blog post
description: This is my first blog post on algolia nuxt
tags: [first, lorem ipsum, Iusto] --- ## Lorem ipsum Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Assumenda dolor quisquam consequatur distinctio perferendis. ## Iusto nobis nisi repellat magni facilis necessitatibus, enim temporibus. - Quisquam
- assumenda
- sapiente explicabo
- totam nostrum inventore

The area enclosed with --- is the YAML Front Matter which will be used as a custom injected variable that we will access in our template.

Next, we’re going to create a dynamic page which will be used to:

  • Fetch the article content using asyncData which runs before the page has been rendered. We have access to our content and custom injected variables through the context by using the variable $content. As we are using a dynamic page, we can know what article file to fetch using the params.slug variable provided by Vue Router to get the name of each article.
  • Render the article in the template using <nuxt-content>.

Ok, navigate to pages/ and create a blog/ folder. Create a _slug.vue (our dynamic page) file and insert the following:

<!-- pages/blog/_slug.vue --> <template> <article class="prose prose-lg lg:prose-xl p-4 mt-24 m-auto max-w-4xl"> <header> <h1>{{ article.title }}</h1> <p>{{ article.description }}</p> <ul class="list-none"> <li class="inline-block mr-2 font-bold font-monospace" v-for="tag in article.tags" :key="tag" > {{tag}} </li> </ul> </header> <!-- this is where we will render the article contents --> <nuxt-content :document="article" /> </article>
</template> <script>
export default { async asyncData({ $content, params }) { //here, we will fetch the article from the articles/ folder using the name provided in the `params.slug` const article = await $content('articles', params.slug).fetch() //return `article` which contains our custom injected variables and the content of our article return { article } },

If you go to your browser and navigate to http://localhost:3000/blog/first-blog-post you should see our rendered content:

Now that our dynamic page is working and our article is rendering, let’s create some duplicates for the purpose of this tutorial.

<!-- content/articles/second-blog-post.md --> --- title: My first blog post
description: This is my first blog post on algolia nuxt
tags: [first, Placeat amet, Iusto] --- ## Lorem ipsum Lorem ipsum dolor sit amet consectetur, adipisicing elit.
Assumenda dolor quisquam consequatur distinctio perferendis. ## Iusto nobis nisi repellat magni facilis necessitatibus, enim temporibus. - Quisquam
- assumenda
- sapiente explicabo
- totam nostrum inventore

Create Blog Page To List Our Articles

Let’s now create a blog page to list our articles. This is also where our search bar will live. Create a new file pages/blog/index.vue.

<!-- pages/blog/index.vue --> <template> <main> <section class="p-4 mt-24 m-auto max-w-4xl"> <header> <h1 class="font-black text-2xl">All posts</h1> <!-- dummy search bar --> <div class="search-cont inline-flex gap-2 bg-white p-2 rounded-lg shadow-lg"> <input class="px-2 outline-none" type="search" name="search" id="search"> <button class="bg-blue-600 text-white px-2 rounded-md" type="submit">Search</button> </div> </header> <ul class="prose prose-xl"> <!-- list out all fetched articles --> <li v-for="article in articles" :key="article.slug"> <nuxt-link :to="{ name: 'blog-slug', params: { slug: article.slug } }"> <h2 class="mb-0">{{ article.title }}</h2> <p class="mt-0">{{ article.description }}</p> </nuxt-link> </li> </ul> </section> </main>
</template> <script>
export default { async asyncData({ $content }) { // fetch all articles in the folder and return the: const articles = await $content('articles') // title, slug and description .only(['title', 'slug', 'description']) // sort the list by the `createdAt` time in `ascending order` .sortBy('createdAt', 'asc') .fetch() return { articles } },

Here, in our asyncData function, when fetching $content('articles') we chain .only(['title', 'slug', 'updatedAt', 'description']) to fetch only those attributes from the articles, .sortBy('createdAt', 'asc') to sort it and lastly fetch() to fetch the data and assign it to const articles which we then return.

So, in our <template>, we can the list of articles and create links to them using their slug property.

Our page should look something like this:

Great 🍻

Install And Set Up Algolia Search And Vue-instantSearch

Now that we’ve gotten the basic stuff out of the way, we can integrate Algolia Search into our blog site.

First, let’s install all the packages we will be needing:

#install dependencies npm install vue-instantsearch instantsearch.css algoliasearch nuxt-content-algolia remove-markdown dotenv
  • vue-instantsearch
    Algolia InstantSearch UI component/widget library for Vue.
  • instantsearch.css
    Custom styling for instantSearch widgets.
  • algoliasearch
    A HTTP client to interact with Algolia.
  • nuxt-content-algolia
    Package for indexing our content and sending it to Algolia.
  • remove-markdown
    This strips all markdown characters from the bodyPlainText of the articles.
  • dotenv
    This helps to read environment variables from .env files.

We’ll be using these packages throughout the rest of this tutorial, but first, let’s set up an Algolia account.

Set Up Algolia Account

Sign up for an Algolia account at https://www.algolia.com/. You can do this for free, however, this will give you a trial period of 14days. Since we’re not performing heavy tasks with Algolia, their free tier will do just fine for our project after the trial expires.

You’ll be taken through some onboarding steps. After that, an UNAMED APP will be created for you. On the sidebar, on the left, navigate to the API Keys you’ll be provided with:

  • Application ID
    This is your unique application identifier. It’s used to identify you when using Algolia’s API.
  • Search Only API Key
    This is the public API key to use in your frontend code. This key is only usable for search queries and sending data to the Insights API.
  • Admin API Key
    This key is used to create, update and DELETE your indices. You can also use it to manage your API keys.

Now that we have our API Keys, let’s save them in an .env file for our project. Navigate to the project root folder and create a new file .env and enter your API keys:

.env ALGOLIA_APP_ID=algolia-app-id

Replace algolia-app-id and algolia-admin-api-key with your Application ID and Admin API Key respectively.

Create An 'Articles' Index For Our Nuxt Articles In Algolia

On your Algolia account, go to Indices and click on create Index. Then enter the name of your index and we’ll be using articles for this tutorial.

As you can see, our 'article' index has been created.

Set Up nuxt-content-algolia To Send Content Index To Algolia

We’ve successfully created an index property on our account. Now we have to generate an index from our Nuxt articles which is what Algolia will use to provide results for search queries. This is what the nuxt-content-algolia module that we previously installed is for.

We need to configure it in our nuxt.config.js.

First, we will add it to our buildModules:

// nuxt.config.js ... // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: ['@nuxtjs/tailwindcss', 'nuxt-content-algolia'], ...

Then, we create a new nuxtContentAlgolia object and add a few configurations to it:

// nuxt.config.js export default {
... nuxtContentAlgolia: { // Application ID appId: process.env.ALGOLIA_APP_ID, // Admin API Key // !IMPORTANT secret key should always be an environment variable // this is not your search only key but the key that grants access to modify the index apiKey: process.env.ALGOLIA_ADMIN_API_KEY, paths: [ { name: 'articles', index: process.env.ALGOLIA_INDEX || 'articles', fields: ['title', 'description', 'tags', 'bodyPlainText'] } ]
}, ...

The nuxtContentAlgolia takes in the following properties:

  • appId
    Application ID*.
  • apiKey
    Admin API Key.
  • paths
    An array of index objects. This is where we define where we want to generate indexes from. Each object takes the following properties:
    • name
      The name of the folder within the content/ folder. In other words, we’ll be using files within content/articles/ since we defined the name as 'articles'.
    • index
      This is the name of the index we created on our Algolia dashboard.
    • fields
      An array of fields to be indexed. This is what Algolia will base its search queries on.

Generate bodyPlainText From Articles

Note that in the fields array, we have bodyPlainText as one of its values. Nuxt Content does not provide such a field for us. Instead, what Nuxt Content provides is body which is a complex object that will be rendered in the DOM.

In order to get our bodyPlainText which is simply all text, stripped of markdown and HTML characters, we have to make use of yet another package, remove-markdown.

To use the remove-markdown function we need to make use of Nuxt hooks. We’ll use the 'content:file:beforeInsert' hook which allows you to add data to a document before it is inserted, to strip off the markdown and add the generated plain text to bodyPlainText.

// nuxt.config.js export default {
... hooks: { 'content:file:beforeInsert': (document)=>{ const removeMd = require('remove-markdown'); if(document.extension === '.md'){ document.bodyPlainText = removeMd(document.text); } }
}, ...

In the 'content:file:beforeInsert' hook, we get the remove-markdown package. Then we check if the file to be inserted is a markdown file. If it is a markdown file, we generate the plain text by calling removeMd which takes document.text — the text of our content, as an argument, which we assign to a new document.bodyPlainText property. The property will now be available for use through Nuxt Content.

Great! Now that that’s done, we can generate the index and send it over to Algolia.

Confirm Algolia Index

Alright. We’ve set up nuxt-content-algolia and we’ve generated bodyPlainText for our articles. We can now generate this index and send the data over to Algolia by building our project using nuxt generate.

npm run generate

This will start building our project for production and run the nuxtContentAlgolia config. When we look at our terminal after the build we should see that our content has been indexed and sent to Algolia.

To verify, you can go to your Algolia dashboard:

Open Indices, then go to Search API logs, where you will see a log of operations performed with your Search API. You can now open and check the API call sent from your Nuxt project. This should have the content of your article as specified in the fields section of nuxtContentAlgolia config.

Nice! 🍻

Building The Search UI

So far we’ve been able to generate and send index data to Algolia, which means that we are able to query this data to get search results.

To do that within our app, we have to build our search UI.

Vue-InstantSearch provides lots of UI components using Algolia that can be integrated to provide a rich search experience for users. Let’s set it up.

Create And Configure vue-instantSearch Plugin

In order to use the Algolia InstantSearch widgets in our Nuxt app, we will have to create a plugin in our plugins folder.

Go to plugins/ and create a new file vue-instantsearch.js.

// plugins/vue-instantsearch.js import Vue from 'vue'
import InstantSearch from 'vue-instantsearch' Vue.use(InstantSearch)

Here, we’re simply importing InstantSearch and using it on the Vue frontend.

Now, we have to add the vue-instantSearch plugin to our plugins and build options in nuxt.config.js in order to transpile it to Vue.js.

So, go over to nuxt.config.js and add the following:

// nuxt.config.js export default {
... // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: ['@/plugins/vue-instantsearch.js'], // Build Configuration: https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-build#transpile
build: { transpile: ['vue-instantsearch', 'instantsearch.js/es']
} ...

InstantSearch code uses ES modules, yet it needs to be executed in Node.js. That’s why we need to let Nuxt know that those files should be transpiled during the build.
Now that we’ve configured our vue-instantSearch plugin, let’s create a search component.

Create A Search Component

Create a new file components/Search.vue.

Since we’ve installed vue-instantSearch as a plugin, we can use it within our Vue components.

<!-- components/Search.vue --> ... <script>
import algoliaSearch from 'algoliasearch/lite'
import 'instantsearch.css/themes/satellite-min.css' // configurations for Algolia search
const searchClient = algoliaSearch( // Applictaion ID '34IIDW6KKR', // Search API key '3f8d80be6c42bb030d27a7f108eb75f8'
export default { data(){ return{ searchClient } }

First, in the <script> section, we’re importing algoliaSearch and instantsearch.css.

Next, we provide the credentials for our Algolia search which are:

  • Application ID,
  • Search API key.

As parameters to algoliaSearch then assign it to searchClient which we will use in our <template> to configure our Algolia search widgets.

ais-instant-search Widget

ais-instant-search is the root Vue InstantSearch component. All other widgets need to be wrapped with the root component to function. The required attributes for this component are:

  • index-name
    Name of the index to query, in this case, it would be articles.
  • search-client
    algoliaSearch object containing Application ID and Search API Key.
<!-- components/Search.vue --> <template> <div class="search-cont inline-flex gap-2 bg-white p-2 rounded-lg shadow-lg"> <ais-instant-search index-name="articles" :search-client="searchClient"> </ais-instant-search> </div>
</template> ...

ais-configure Widget

The ais-configure widget helps configure the search functionality by sending defined parameters to Algolia.

Any props you add to this widget will be forwarded to Algolia. For more information on the different parameters you can set, have a look at the search parameters API reference.

The parameters we’ll set for now will be:

  • attributesToSnippet
    The name of the attribute or field to snippet in, we’ll soon see more on this.
  • hits-per-page.camel
    Number of results in one page.
  • snippetEllipsisText="…"
    Set ... before and after snipped text.
<!-- components/Search.vue --> <template> <div class="search-cont inline-flex gap-2 bg-white p-2 rounded-lg shadow-lg"> <ais-instant-search index-name="articles" :search-client="searchClient"> <ais-configure :attributesToSnippet="['bodyPlainText']" :hits-per-page.camel="5" snippetEllipsisText="…" > </ais-configure> </ais-instant-search> </div>
</template> ...

ais-autocomplete Widget

This widget is basically a wrapper that allows us to create a search result that autocompletes the query. Within this widget, we can connect to other widgets to provide a richer UI and access multiple indices.

<!-- components/Search.vue --> <template> <div class="search-cont inline-flex gap-2 bg-white p-2 rounded-lg shadow-lg"> <ais-instant-search index-name="articles" :search-client="searchClient"> <ais-configure :attributesToSnippet="['bodyPlainText']" :hits-per-page.camel="5" snippetEllipsisText="…" > <ais-autocomplete> <template v-slot="{ currentRefinement, indices, refine }"> <input type="search" :value="currentRefinement" placeholder="Search for an article" @input="refine($event.currentTarget.value)" /> <ais-stats /> <template v-if="currentRefinement"> <ul v-for="index in indices" :key="index.indexId"> <li> <h3>{{ index.indexName }}</h3> <ul> <li v-for="hit in index.hits" :key="hit.objectID"> <h1> <ais-highlight attribute="title" :hit="hit" /> </h1> <h2> <ais-highlight attribute="description" :hit="hit" /> </h2> <p> <ais-snippet attribute="bodyPlainText" :hit="hit" /> </p> </li> </ul> </li> </ul> </template> <ais-pagination /> </template> </ais-autocomplete> </ais-configure> </ais-instant-search> </div>
</template> ...

So, within our ais-autocomplete widget, we’re doing a few things:

  • Overriding the DOM output of the widget using the default slot. We’re doing this using the scopes:
    • currentRefinement: string: the current value of the query.
    • indices: object[]: the list of indices.
    • refine: (string) => void: the function to change the query.
<template v-slot="{ currentRefinement, indices, refine }">
  • Create a search <input> to hold, change the query and value of the currentRefinement.
<input type="search" :value="currentRefinement" placeholder="Search for an article" @input="refine($event.currentTarget.value)"
  • Render the search results for each index. Each index has the following properties:
    • indexName: string: the name of the index.
    • indexId: string: the id of the index.
    • hits: object[]: the resolved hits from the index matching the query.
<template v-if="currentRefinement"> <ul v-for="index in indices" :key="index.indexId"> <li> <h3>{{ index.indexName }}</h3> ...
  • Then render the results — hits.
<ul> <li v-for="hit in index.hits" :key="hit.objectID"> <h1> <ais-highlight attribute="title" :hit="hit" /> </h1> <h2> <ais-highlight attribute="description" :hit="hit" /> </h2> <p> <ais-snippet attribute="bodyPlainText" :hit="hit" /> </p> </li>
</ul> ...

Here’s what we’re using:

  • <ais-highlight>
    Widget to highlight the portion of the result which directly matches the query of the field passed to the attribute prop.
  • <ais-snippet>
    Widget to display the relevant section of the snippeted attribute and highlight it. We defined the attribute in attributesToSnippet in <ais-configure>.

Let’s run our dev server and see what our New search looks like.

Styling Our Search Component

InstantSearch comes with some default styles that we included in our project using the instantsearch.css package. However, we might need to change or add some styles to our components to suit the site we’re building.

The CSS classes with many widgets can be overwritten using the class-names prop. For example, we can change the highlighted style of <ais-highlight>.

<!-- components/Search.vue --> ...
<h1> <ais-highlight :class-names="{ 'ais-Highlight-highlighted': 'customHighlighted', }" attribute="title" :hit="hit" />
</h1> ...

And in our CSS:

<!-- components/Search.vue --> ... <style> .customHighlighted { @apply text-white bg-gray-600; }

We see that the class we defined has been applied to the highlight.

So, I’ll go ahead and style it using tailwind till I feel it looks good.

<!-- components/Search.vue --> <template> <div class="search-cont relative inline-flex mt-6 bg-gray-100 border-2 rounded-lg focus-within:border-purple-600"> <ais-instant-search-ssr index-name="articles" :search-client="searchClient"> <ais-configure :attributesToSnippet="['bodyPlainText']" :hits-per-page.camel="5"> <ais-autocomplete class="wrapper relative"> <div slot-scope="{ currentRefinement, indices, refine }"> <input class="p-2 bg-white bg-opacity-0 outline-none" type="search" :value="currentRefinement" placeholder="Search for an article" @input="refine($event.currentTarget.value)" /> <div class="results-cont relative"> <div class=" absolute max-h-96 overflow-y-auto w-96 top-2 left-0 bg-white border-2 rounded-md shadow-lg" v-if="currentRefinement"> <ais-stats class="p-2" /> <ul v-for="index in indices" :key="index.indexId"> <template v-if="index.hits.length > 0"> <li> <h2 class="font-bold text-2xl p-2"> {{ index.indexName }} </h2> <ul> <li class="border-gray-300 border-t p-2 hover:bg-gray-100" v-for="hit in index.hits" :key="hit.objectID" > <nuxt-link :to="{ name: 'blog-slug', params: { slug: hit.objectID }, }" > <h3 class="font-extrabold text-xl"> <ais-highlight :class-names="{ 'ais-Highlight-highlighted': 'customHighlighted', }" attribute="title" :hit="hit" /> </h3> <p class="font-bold"> <ais-highlight :class-names="{ 'ais-Highlight-highlighted': 'customHighlighted', }" attribute="description" :hit="hit" /> </p> <p class="text-gray-500"> <ais-snippet :class-names="{ 'ais-Snippet-highlighted': 'customHighlighted', }" attribute="bodyPlainText" :hit="hit" /> </p> </nuxt-link> </li> </ul> </li> </template> </ul> </div> </div> </div> </ais-autocomplete> </ais-configure> </ais-instant-search-ssr> </div>
</template> ... <style>
.customHighlighted { @apply text-purple-600 bg-purple-100 rounded p-1;

Alright, the styling is done and I’ve included a <nuxt-link> to route to the article on click.

<nuxt-link :to="{ name: 'blog-slug', params: { slug: hit.objectID }}">

We now have something like this:

Configuring InstantSearch For Server-Side Rendering (SSR)

We now have our search component up and running but it only renders on the client-side and this means we have to wait for the search component to load even after the page loads. We can further improve the performance of our site by rendering it on the server-side.

According to Algolia, the steps for implementing server-side rendering are:

On the server:

  • Make a request to Algolia to get search results.
  • Render the Vue app with the results of the request.
  • Store the search results on the page.
  • Return the HTML page as a string.

On the client:

  • Read the search results from the page.
  • Render (or hydrate) the Vue app with search results.

Using Mixins, serverPreFetch, beforeMount

Following Algolia’s documentation on implementing SSR with Nuxt, we have to make the following changes:

<!-- components/Search.vue --> ...
// import 'vue-instantsearch';
import { createServerRootMixin } from 'vue-instantsearch' import algoliaSearch from 'algoliasearch/lite'
import 'instantsearch.css/themes/satellite-min.css' const searchClient = algoliaSearch( '34IIDW6KKR', '3f8d80be6c42bb030d27a7f108eb75f8'
) export default { data() { return { searchClient, } }, mixins: [ createServerRootMixin({ searchClient, indexName: 'articles', }), ], serverPrefetch() { return this.instantsearch.findResultsState(this).then((algoliaState) => { this.$ssrContext.nuxt.algoliaState = algoliaState }) }, beforeMount() { const results = (this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) || window.__NUXT__.algoliaState this.instantsearch.hydrate(results) // Remove the SSR state so it can’t be applied again by mistake delete this.$nuxt.context.nuxtState.algoliaState delete window.__NUXT__.algoliaState },

We’re simply doing the following:

  • createServerRootMixin to create a reusable search instance;
  • findResultsState in serverPrefetch to perform a search query on the back end;
  • hydrate method in beforeMount.

Then in our <template>,

<!-- components/Search.vue --> ...
<ais-instant-search-ssr index-name="articles" :search-client="searchClient"> ...

Here, we to replace ais-instant-search with ais-instant-search-ssr.


We’ve successfully built a Nuxt site with some content handled by Nuxt Content and integrated a simple Algolia search into our site. We’ve also managed to optimize it for SSR. I have a link to the source code of the project in this tutorial and a demo site deployed on Netlify, the links are down below.

We have tons of options available to customize and provide a rich search experience now that the basics are out of the way. The Algolia widgets showcase is a great way to explore those options and widgets. You’ll also find more information on the widgets used in this tutorial.

GitHub Source Code

Further Reading

Here are some links that I think you will find useful:

Should Merchants Launch Their Own Marketplaces?

For manufacturers, distributors, and enterprise retailers, owning a marketplace could be a competitive advantage and a key to business growth.

“​​Marketplaces have been hot for a long time. A prime example is Amazon, which is one of the largest ecommerce sites in the world — with 56% of its unit sales coming from marketplace sellers,” said Mike Shapaker, the chief marketing officer at ChannelAdvisor, a software firm that provides marketplace integrations.

“Globally, there are many pure-play marketplaces, such as eBay. Even established retailers, like Walmart and Target, have added marketplaces, so this trend shows no signs of slowing down,” Shapaker said.

Marketplaces are not limited to consumer ecommerce.

“Online marketplaces are an area we’ve seen growth over the last couple of years,” said Paul do Forno, managing director of content and commerce at Deloitte, the international consulting firm. “We see a huge amount of B2B marketplaces. Clients are growing with adjacent products. They’re adding marketplaces where they want to own the customer relationship.”

Marketplace Experience

Marketplaces are popular with retailers and B2B sellers because they may benefit all of the parties involved.

Shoppers, for example, have many reasons to like online marketplaces.

“Marketplaces provide a shopping experience with brands consumers know, making it easier to find products — easier in the sense that marketplaces with massive catalogs have virtually everything. On the flip side, niche marketplaces specialize in certain categories and can bring expertise. With marketplaces like Amazon, the turnaround time is fast so that consumers can have their purchases within a few days or even the same day. Most have guarantees around transactions, which bring a level of safety and comfort to consumers,” said ChannelAdvisor’s Shapaker.

Sellers like them too.

For a manufacturer, distributor, or retailer, a marketplace strategy may lead to more profit, deeper relationships with customers — as Deloitte’s do Forno noted — and a way to separate the business from its competitors.

More Profit

“A marketplace impacts many functions within a company. It allows the [chief financial officier] to look at the gross margin differently because you don’t just make margin reselling products you make or buy. You make margin on being an intermediary,” said Adrien Nussenbaum, co-founder and CEO of Mirakl, which provides a leading marketplace software platform.

“It requires people in supply chain fulfillment to think about what they really need to have in their warehouses. Marketing people need to think about the ability they now have to drive traffic and extend customer experiences,” continued Nussenbaum.

Home page of MadewellHome page of Madewell

Madewell uses Mirakl’s marketplace platform and is an example of a retailer using marketplaces.

Customer Relationships

A marketplace may also help a business deepen customer relationships.

First, there is the obvious ability to offer more products or more options to customers. An omnichannel retailer may offer a few items within a given product category, but a marketplace is likely to have a much larger selection.

Second, related products sold in a marketplace might make it easier for customers to use a business’s core products. Nussenbaum noted that Airbus Helicopters, a division of the France-based airplane manufacturer, had created a private marketplace for its customers to purchase parts, fluids, and supplies needed to maintain a helicopter.

This marketplace was convenient for Airbus customers, and it allowed the company to encourage maintenance and safety and deepen its relationships with businesses that supply those adjacent products.

Home page of Airbus HelicoptersHome page of Airbus Helicopters

Airbus Helicopter’s marketplace helps the company deepen its customer relationships and engage businesses that supply helicopter maintenance items.

Third, marketplaces make it possible to adapt to changing customer needs.

An online clothing store, for example, could offer second-hand items via its marketplace, responding to the demand for more environmentally sustainable offerings.


If it is true that a good marketplace strategy can lead to relatively more profit and closer customer relationships, it follows that a marketplace could afford a manufacturer, distributor, or enterprise retailer a competitive advantage.

“Marketplaces are a natural extension of commerce — B2C and B2B,” said Nussenbaum. “And marketplaces are a key factor in the future survival of businesses.”

Or, as Deloitte’s do Forno put it, “If you don’t build a marketplace and make it easier for your customer and solve their problem, there is going to be a new place or pure-play that is going to pop up and take your business, your vertical.”

But owning a marketplace, as do Forno and Nussenbaum suggest, does not guarantee success. It can instead add a new level of competition, including competing for sellers.

Adding a marketplace to an existing commerce business requires a company to treat sellers on the marketplace like customers too. After all, they are paying fees. Those fees must be competitive, and the value the marketplace brings to sellers in terms of demand outweighs the distance, if you will, it creates between that seller and the ultimate customers.

As an example, think about the love-hate relationship some marketplace sellers have with Amazon. On the one hand, those sellers use the marketplace to drive sales. But at the same time, they complain about losing touch with customers, relatively lower margins, and the fear that Amazon might use its marketplace data to identify a popular product and copy it.

Thus, successful marketplace owners will need to compete for sellers too.

September 2021 Top 10: Our Most Popular Posts

Since 2005 we’ve published thousands of articles, webinars, and podcast episodes to assist ecommerce merchants. What follows are the most popular articles that we published in September 2021. Articles from early in the month are more likely to make the list than later ones.

21 New Social Media Tools for Merchants in 2021

New and updated social media tools can enhance an ecommerce business. Here is a list of new social media tools and platform updates in 2021. There are tools for shopping, influencer marketing, live streaming, payments, dynamic advertising, and more. Read more…

5 Content Marketing Ideas for October 2021

In October 2021, your content marketing could feature product-focused tutorials, storytelling, and annual observances, such as a little holiday called Halloween. What follows are five content marketing ideas your business can try in October 2021. Read more…

11 Product Page Features to Drive Conversions

When it comes to compelling product pages, less can be more. Cluttered pages distract from selling points. Focus on the crucial details. Embrace a minimalist approach to lessen the thinking process and close the sale. What follows are 11 product page features from three online stores. Read more…

3 Ways to Reduce Shipping Costs

Lowering shipping costs is a surefire way to improve profits. Same-day delivery, free shipping, standard transit, click-and-collect — all are candidates for cost reductions. Read more…

3 Optimizations for Google Shopping Campaigns

The benefits of Google Shopping are compelling. Advertisers consistently experience strong revenue, traffic, and return on ad spend while often paying less per click than standard search campaigns. Still, the performance of Google Shopping ads, like all ads, could plateau. Thus continuous optimizations are key to sustaining results. Read more…

12 New Ecommerce Books for Fall 2021

Here’s a batch of new ecommerce books for your fall reading list. There are titles on branding, social media marketing, personalized customer experience, entrepreneurship, and the post-pandemic workplace. Read more…

Ramping Up Google Analytics 4

I first addressed Google Analytics 4 last fall. It is becoming the go-to platform in Google’s analytics ecosystem. The older version, Universal Analytics, will eventually sunset. For now, continue using Universal Analytics while ramping up GA4. Read more…

Ecommerce Product Releases: September 16, 2021

Here is a list of product releases and updates for mid-September from companies that offer services to online merchants. There are updates on team productivity tools, small business financing, subscription sales, logistics, and managing campaigns on Amazon. Read more…

Remove These Distractions to Lower Cart Abandons

Cart abandonment rates remain high. I’ve seen estimates in 2021 ranging from 50% to 80%, depending on the product and industry. A common culprit is sticker shock from excessive shipping and handling fees. Another is distractions: unnecessary or confusing checkout fields. In this post, I’ll address steps to streamline checkouts to save the sale. Read more…

A Perfect Storm for 2021 Holiday Email Marketing

Labor shortages, pent-up consumer demand, and supply chain disruptions could dramatically impact retailers this holiday season. Those developments, coupled with privacy changes on Apple’s new iOS 15, mean email marketers face a perfect storm of uncertainty. Read more…

A Time Of Transition (October 2021 Desktop Wallpapers Edition)

Inspiration lies everywhere. In the fall leaves shining in the most beautiful colors these days, in the misty mornings and golden afternoons that October might bring along, or taking a walk on a windy day. Whatever it is that gets you inspired, our monthly wallpapers series is bound to give you a little inspiration boost, too.

For this October edition, artists and designers from across the globe once again challenged their creative skills and designed wallpapers to spark your imagination and make the month a bit more colorful as it already is. Just like every month since we embarked on this creativity mission more than ten years ago.

The wallpapers all come in versions with and without a calendar for October 2021 — so no matter if you want to keep an eye on your deadlines or plan to use your favorite design even after the month has ended, we’ve got you covered. At the end of this post, you’ll also find some oldies but goodies from our wallpapers archives. A huge thank-you to everyone who shared their artworks with us! Happy October!

  • You can click on every image to see a larger preview,
  • We respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience through their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us but rather designed from scratch by the artists themselves.

Submit a wallpaper!

Did you know that you could get featured in our next wallpapers post, too? We are always looking for creative talent! Join in! →

Autumn Vibes

“Autumn has come, the time of long walks in the rain, weekends spent with loved ones, with hot drinks, and a lot of tenderness. Enjoy.” — Designed by LibraFire from Serbia.

The Night Drive

Designed by Vlad Gerasimov from Russia.

The Return Of The Living Dead

Designed by Ricardo Gimenes from Sweden.

Spooky Season

“The days become shorter… The socks thicker… And the ghosts louder! The spooooooky season is upon us, and we can’t wait to carve into it. ActiveCollab wishes you a bag full of treats this October!” — Designed by ActiveCollab from the USA.

Up And Down San Francisco

“It’s October and we go to San Francisco. We enjoy the sun and we go up and down with the cable car.” — Designed by Veronica Valenzuela from Spain.

Goddess Makosh

“At the end of the kolodar, as everything begins to ripen, the village sets out to harvesting. Together with the farmers goes Makosh, the Goddess of fields and crops, ensuring a prosperous harvest. What she gave her life and health all year round is now mature and rich, thus, as a sign of gratitude, the girls bring her bread and wine. The beautiful game of the goddess makes the hard harvest easier, while the song of the farmer permeates the field.” — Designed by PopArt Studio from Serbia.

Smashing Halloween

Designed by Ricardo Gimenes from Sweden.

Oldies But Goodies

Hidden in our wallpapers archives, we rediscovered some almost-forgotten treasures from past October editions. May we present… (Please note that these designs don’t come with a calendar.)

Hello Autumn

“Did you know that squirrels don’t just eat nuts? They really like to eat fruit, too. Since apples are the seasonal fruit of October, I decided to combine both things into a beautiful image.” — Designed by Erin Troch from Belgium.

Bird Migration Portal

“October is a significant month for me because it is when my favorite type of bird travels south. For that reason I have chosen to write about the swallow. When I was young, I had a bird’s nest not so far from my room window. I watched the birds almost every day; because those swallows always left their nests in October. As a child, I dreamt that they all flew together to a nicer place, where they were not so cold.” — Designed by Eline Claeys from Belgium.

Game Night And Hot Chocolate

“To me, October is all about cozy evenings with hot chocolate, freshly baked cookies, and a game night with friends or family.” — Designed by Lieselot Geirnaert from Belgium.

Magical October

“‘I’m so glad I live in a world where there are Octobers.’ (L. M. Montgomery, Anne of Green Gables)” — Designed by Lívi Lénárt from Hungary.

Haunted House

“Love all the Halloween costumes and decorations!” — Designed by Tazi from Australia.

First Scarf And The Beach

“When I was little, my parents always took me and my sister for a walk at the beach in Nieuwpoort, we didn’t really do those beach walks in the summer but always when the sky started to turn grey and the days became colder. My sister and I always took out our warmest scarfs and played in the sand while my parents walked behind us. I really loved those Saturday or Sunday mornings where we were all together. I think October (when it’s not raining) is the perfect month to go to the beach for ‘uitwaaien’ (to blow out), to walk in the wind and take a break and clear your head, relieve the stress or forget one’s problems.” — Designed by Gwen Bogaert from Belgium.

Shades Of Gold

“We are about to experience the magical imagery of nature, with all the yellows, ochers, oranges, and reds coming our way this fall. With all the subtle sunrises and the burning sunsets before us, we feel so joyful that we are going to shout it out to the world from the top of the mountains.” — Designed by PopArt Studio from Serbia.

Flying Home For Halloween

“You can only fully master the sky wearing an aviator hat and goggles. Like this little bat, flying home to celebrate Halloween with his family and friends.” — Designed by Franke Margrete from the Netherlands.


“To me, October is a transitional month. We gradually slide from summer to autumn. That’s why I chose to use a lot of gradients. I also wanted to work with simple shapes, because I think of October as the ‘back to nature/back to basics month’.” — Designed by Jelle Denturck from Belgium.

Fallen Woods

Designed by Dan Ioanitescu from Canada.

Dope Code

“October is the month when the weather in Poland starts to get colder, and it gets very rainy, too. You can’t always spend your free time outside, so it’s the perfect opportunity to get some hot coffee and work on your next cool web project!” — Designed by Robert Brodziak from Poland.

Autumn Gate

“The days are colder, but the colors are warmer, and with every step we go further, new earthly architecture reveals itself, making the best of winters’ dawn.” — Designed by Ana Masnikosa from Belgrade, Serbia.


Designed by Ricardo Gimenes from Sweden.

Autumn In The Forest

“Autumn is a wonderful time to go for walks in the forest!” — Designed by Hilda Rytteke from Sweden.

Spooky Town

Designed by Xenia Latii from Germany.


“The term ‘Hanlu’ literally translates as ‘Cold Dew.’ The cold dew brings brisk mornings and evenings. Eventually the briskness will turn cold, as winter is coming soon. And chrysanthemum is the iconic flower of Cold Dew.” — Designed by Hong, ZI-Qing from Taiwan.

Discovering The Universe!

“Autumn is the best moment for discovering the universe. I am looking for a new galaxy or maybe… a UFO!” — Designed by Verónica Valenzuela from Spain.


Designed by Ricardo Delgado from Mexico City.

Rain And Acorns

“Waiting at the bus stop when it’s raining in October can be a sad and wet experience. The bus is late, the dry spot is taken by other people and you’re just standing there in the rain with your hands in your pockets with nowhere to go. Acorns must have a hard time like that too! Waiting in the rain for the squirrels to come and pick them up.” — Designed by Casey Dulst from Belgium.

Create More

“The colors of the sun inspired me.” — Designed by Hitesh Puri from India.