How to Use the GA4 Add-On in Google Sheets (2023)

To use the add-on:

Open Google Sheets and a new sheet

Click on “Extensions” in the top menu bar (to the right)

Click “Get add-ons”

Search for “GA4 Reports Builder for Google Analytics”

Select Google’s Add On and click Install and allow access to data (at your own risk etc)

You might have to wait a minute or two for the add on to install – refresh the page if it’s not found in the “Extensions” menu at the top of the page

Once installed – click “Extensions”

Select the “GA4 Reports Builder for Google Analytics” extension

Click “create new report”

Fill in the “table” of drop down menus on the right hand side with the GA4 property you want and the dimensions and metrics

click “Create Report”

Select the GA4 Add on in the Extensions Menu again

Click “Run Report” in the side-menu that pops up when you hover/select the GA4 add-on under “Extensions” menu

You’ll be give a table of data.

Highlight the table of data and all it’s cells

Click “Insert” from top menu bar and then – Chart

Right click the chart and you can change the Chart Type and design.

When you click “Create Report” you should see a load of data in the Sheet about the property etc ^

(if you don’t try again in a new Sheet)

Click “Extensions” menu again – choose the GA4 Add-on and click “run report”:

Select the table of data – right click – go to “Insert” menu and choose “Chart”:

Right click the chart – click “chart style” than click “setup” to change the chart type:

ga4 add on

Making a CSS or element responsive on Mobile

This is what I used to make a container ‘work’ on mobile – it was working fine on desktop, but the text was overlaying the image on mobile:

<style>
  @media screen and (max-width:768px) {
    .name-of-div-container {
      position: relative;
      transform: none;
    }
  }
</style>

On screens that are 768px or less (i.e. mobile phones),
The “name-of-div-container” should be positioned relative with no transform styling.

This is quite specific code for the example I was using but you can replace position relative and transform none with whatever you want the element to do on a mobile device.

Table with Responsive Horizontal Scroll Bar – HTML & CSS

This is what I’ve just used.

The table in the <body> on the HTML doc:

 <!--table-->  
  <div class="content-text section" style="padding-top: 0px; padding-bottom: 1vh; margin-top: 1rem; width:870px; max-width: 100%;">
<!--<div class="scroll-container scroll-x">-->
  <div style="clear: both;">
    <div class="scroll-container">
    <div class="content-2 section " style="margin-top: 1px">
     <div>
    <table class="brand-p1" style= "margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; text-align: center;     border-collapse: collapse; background-color:transparent; font-size: 13px; width:870px; max-width: 100%;">
    
   <tr>
     <th colspan="8" style="border: 1px solid #878681;background-color:transparent;font-size: 15px;"><strong>Regulation Lacrosse Ball Dimensions &amp; Properties Recap</strong></th></tr>
     <tr style=" border: 1px solid #878681;background-color:transparent; font-size: 14px;">
     <th style="border: 1px solid #878681;background-color:transparent;">Width</th>
     <th style="border: 1px solid #878681;background-color:transparent;">Radius</th>
     <th style="border: 1px solid #878681;background-color:transparent;">Weight</th>
     <th style="border: 1px solid #878681;background-color:transparent;">Colour</th>
     <th style="border: 1px solid #878681;background-color:transparent;">Material</th>
   </tr>

    <tr style=" border: 1px solid #878681;background-color:transparent;">
    <td style="border: 1px solid #878681;background-color:transparent; ">7.75 - 8 inches (19.69 - 20.32cm)</td>
    <td style="border: 1px solid #878681;background-color:transparent; ">2.47 -2.55 inches (6.27 - 6.47cm)</td>
    <td style="border: 1px solid #878681;background-color:transparent; ">5.0 -5.25 oz (141.75 - 155.92g)</td>
    <td style="border: 1px solid #878681;background-color:transparent; ">White, Yellow or Orange</td>
    <td style="border: 1px solid #878681;background-color:transparent; ">Vulcanised rubber</td>
   </tr>

   
     </table>
    </div> 
   </div> 
  </div>
  </div>
    </div>

 <!--table-->       

And then this CSS:

<style>
/* Default - For larger screens */
.scroll-container {
    overflow-x: hidden;
}

/* For screens with a max-width of 600px */
@media screen and (max-width: 600px) {
    .scroll-container {
        overflow-x: scroll;
    }
}
</style>  

CollectionPage Schema Markup Example (eCommerce)

Here’s an eCommerce example, that I found on StackOverflow:

<script type="application/ld+json">
{
  "@context" : "http://schema.org",
  "@type": "CollectionPage",
  "name": "Shopify Apps",
  "url": "https://sherpas.design/pages/shopify-apps",
  "description": "We build apps that function and feel natively Shopify",
  "image": "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",
  "mainEntity" : {
    "@type":"ItemList",
    "itemListElement":[
      {
        "@type":"ListItem",
        "position":1,
        "url":"http://example.com/coffee_cake.html",
         "name":"coffee cake"

      },
      {
        "@type":"ListItem",
        "position":2,
        "url":"http://example.com/apple_pie.html",
        "name":"apple pie"
      },
      {
        "@type":"ListItem",
        "position":3,
        "url":"http://example.com/blueberry-pie.html"
         "name":"blueberry pie"
      }
    ]
  }
}
</script>

(Thanks stackoverflow and sherpa.design)

You can add an image URL below “name” too^

Here’s an example with elatedLink and IsPartOf schema elements:

<script type="application/ld+json">

{
@context: "http://schema.org",
@type: "CollectionPage",
name: "Shopify Apps",
url: "https://sherpas.design/pages/shopify-apps",
description: "We build apps that function and feel natively Shopify",
image: "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",
isPartOf: {
@type: "WebSite",
name: "Sherpas Design",
url: "https://sherpas.design"
},
relatedLink: [
https: //sherpas.design/about-us,
https: //sherpas.design/contact
],
mainEntity: {
@type: "ItemList",
itemListElement: [{
@type: "ListItem",
position: 1,
url: "http://example.com/coffee_cake.html",
name: "Coffee Cake",
image: "http://example.com/images/coffee_cake.jpg"
}, {
@type: "ListItem",
position: 2,
url: "http://example.com/apple_pie.html",
name: "Apple Pie",
image: "http://example.com/images/apple_pie.jpg"
}, {
@type: "ListItem",
position: 3,
url: "http://example.com/blueberry-pie.html",
name: "Blueberry Pie",
image: "http://example.com/images/blueberry_pie.jpg"
}]
}
}
</script>

Here’s an example with a “description” and “brand” – please note – Brand should really be used with Product Schema:

<script type="application/ld+json">

{
"@context": "http://schema.org",
"@type": "CollectionPage",
"name": "Shopify Apps",
"url": "https://sherpas.design/pages/shopify-apps",
"description": "We build apps that function and feel natively Shopify",
"image": "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",
"isPartOf": {
"@type": "WebSite",
"name": "Sherpas Design",
"url": "https://sherpas.design"
},
"relatedLink": [
"https://sherpas.design/about-us",
"https://sherpas.design/contact"
],
"mainEntity": {
"@type": "ItemList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"url": "http://example.com/coffee_cake.html",
"name": "Coffee Cake",
"image": "http://example.com/images/coffee_cake.jpg",
"description": "product is yummy",
"brand": "NanesBakes"
},
{
"@type": "ListItem",
"position": 2,
"url": "http://example.com/apple_pie.html",
"name": "Apple Pie",
"image": "http://example.com/images/apple_pie.jpg",
"description": "product is yummy",
"brand": "MumsCakesYo"
},
{
"@type": "ListItem",
"position": 3,
"url": "http://example.com/blueberry-pie.html",
"name": "Blueberry Pie",
"image": "http://example.com/images/blueberry_pie.jpg",
"description": "product is yummy",
"brand": "NanesBakes"
}
]
}
}
</script>

You can add products as the ‘listitems”, although I’m not 100% sure this is best practice:

<script type="application/ld+json">

{
"@context": "http://schema.org",
"@type": "CollectionPage",
"name": "Shopify Apps",
"url": "https://sherpas.design/pages/shopify-apps",
"description": "We build apps that function and feel natively Shopify",
"image": "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",
"isPartOf": {
"@type": "WebSite",
"name": "Sherpas Design",
"url": "https://sherpas.design"
},
"relatedLink": [
"https://sherpas.design/about-us",
"https://sherpas.design/contact"
],
"mainEntity": {
"@type": "ItemList",
"itemListElement": [
{
"@type": "Product",
"position": 1,
"url": "http://example.com/coffee_cake.html",
"name": "Coffee Cake",
"image": "http://example.com/images/coffee_cake.jpg",
"description": "product is yummy",
"brand": "NanesBakes"
},
{
"@type": "Product",
"position": 2,
"url": "http://example.com/apple_pie.html",
"name": "Apple Pie",
"image": "http://example.com/images/apple_pie.jpg",
"description": "product is yummy",
"brand": "MumsCakesYo"
},
{
"@type": "Product",
"position": 3,
"url": "http://example.com/blueberry-pie.html",
"name": "Blueberry Pie",
"image": "http://example.com/images/blueberry_pie.jpg",
"description": "product is yummy",
"brand": "NanesBakes"
}
]
}
}
</script>

If you are going to embed “Product” schema within itemlist – validator suggests removing the “position” element:

<script type="application/ld+json">

{

"@context": "http://schema.org",

"@type": "CollectionPage",

"name": "Shopify Apps",

"url": "https://sherpas.design/pages/shopify-apps",

"description": "We build apps that function and feel natively Shopify",

"image": "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",

"isPartOf": {

"@type": "WebSite",

"name": "Sherpas Design",

"url": "https://sherpas.design"

},

"relatedLink": [

"https://sherpas.design/about-us",

"https://sherpas.design/contact"

],

"mainEntity": {

"@type": "ItemList",

"itemListElement": [

{

"@type": "Product",

"url": "http://example.com/coffee_cake.html",

"name": "Coffee Cake",

"image": "http://example.com/images/coffee_cake.jpg",

"description": "product is yummy",

"brand": "NanesBakes"

},

{

"@type": "Product",

"url": "http://example.com/apple_pie.html",

"name": "Apple Pie",

"image": "http://example.com/images/apple_pie.jpg",

"description": "product is yummy",

"brand": "MumsCakesYo"

},

{

"@type": "Product",

"url": "http://example.com/blueberry-pie.html",

"name": "Blueberry Pie",

"image": "http://example.com/images/blueberry_pie.jpg",

"description": "product is yummy",

"brand": "NanesBakes"

}

]

}

}

</script>






Here’s is another example – but with more “Product” schema categories/elements – This is now pretty comprehensive, but we could also include “Review”review” “aggregateRating” and “PriceValidUntil”

<script type="application/ld+json">

{
"@context": "http://schema.org",
"@type": "CollectionPage",
"name": "Cakes",
"url": "https://food.com/all-cakes",
"description": "We build cakes that function and feel natively welsh",
"image": "https://cdn.shopify.com/s/files/1/0085/8515/0560/files/logox2_500x500.png?v=1555661781",
"isPartOf": {
"@type": "WebSite",
"name": "Sherpas Design",
"url": "https://sherpas.design"
},
"relatedLink": ["https://food.design/about-us", "https://food.design/contact"],
"mainEntity": {
"@type": "ItemList",
"itemListElement": [{
"@type": "Product",
"url": "http://example.com/coffee_cake.html",
"name": "Coffee Cake",
"image": "http://example.com/images/coffee_cake.jpg",
"description": "product is yummy",
"brand": "NanesBakes",
"sku": "sku1",
"manufacturer": "Manufacturer 1",
"offers": {
"@type": "Offer",
"price": "99.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}, {
"@type": "Product",
"url": "http://example.com/apple_pie.html",
"name": "Apple Pie",
"image": "http://example.com/images/apple_pie.jpg",
"description": "product is yummy",
"brand": "MumsCakesYo",
"sku": "sku2",
"manufacturer": "Manufacturer 2",
"offers": {
"@type": "Offer",
"price": "99.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}, {
"@type": "Product",
"url": "http://example.com/blueberry-pie.html",
"name": "Blueberry Pie",
"image": "http://example.com/images/blueberry_pie.jpg",
"description": "product is yummy",
"brand": "NanesBakes",
"sku": "sku3",
"manufacturer": "Manufacturer 3",
"offers": {
"@type": "Offer",
"price": "99.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
}
}]
}
}
</script>

What is Optional Chaining in JavaScript? (2023)

I asked Chat GPT to explain optional chaining in JavaScript to me like I’m 5:

Alright kiddo, imagine you have a big toy box. Inside this toy box, there are smaller boxes, and inside those, even smaller boxes. Some boxes might be empty, and others might have a toy or another smaller box inside.

Let’s say you want to find a tiny toy car, but you’re not sure which box it’s in. You start with the big toy box, then you open a smaller box inside, and then another one, and so on, until you either find the toy car or reach an empty box.

But there’s a problem. What if you try to open a box and there’s no box inside? You can’t keep looking, right? You’d stop there and say, “Oops! There’s no box here to open.”

In JavaScript, when you want to access something deep inside an object (like our big toy box), you might run into a similar problem. The thing you’re looking for might not be there, and if you try to access it, you’ll get an error.

JavaScript’s optional chaining is like a magic tool that helps with this. Instead of going box by box, and risking an error if something isn’t there, optional chaining lets you say, “Hey, if there’s a box here, open it. If not, that’s okay, just tell me there’s nothing there.”

In code, it looks like this:

let toy = bigToyBox?.smallBox?.tinyBox?.toyCar;

If any of the boxes don’t exist, JavaScript won’t get mad. It’ll just say undefined, meaning “I didn’t find it.” And that’s how optional chaining keeps our toy search safe and error-free!

Optional chaining is an operation in JS that let’s us access values, inside of an object.

Being able to use ?. – allows you to optionally chain things.

It will only try to access the nested value, if the first value existed.

if something doesnt’ exist – it will just return “undefined”

The optional chaining is helpful when working with a database and lots of data, so that you don’t have to manage a load of “if statements”

Optional chaining is a convenient way to access values inside of objects, when working with dynamic data its important that you avoid errors and crashing. Optional chaining can help with this!

SEMRush Guide to the Basics (2023)

SEMRush Tech Audit

Use the tech audit to keep an eye on your website.

You’ll need a Google Search Console account to do an SEMRush tech audit.

Go to “site Audit” near the bottom of the side-menu on the left and then click the “+project” button.

Once the site audit has been carried out, you’ll get a load of metrics that score the health of your website from a tech SEO perspective.

Image from SEMRush.com

Check the notices, warnings and errors, to see what you need to fix.

More info in this blog post.

SEMRush Keyword Gap

Keyword gap – add competitors, see what they are ranking for, that you are not.

Filter by search volume of keywords and keyword difficulty.

Keyword gap is also good for Google ads insights. If a keyword is les than a few dollars per click, but is transactional and difficult to rank for organically, it might be worth bidding on in Google ads.

On Page SEO Checker

Gives you ideas including top pages to optimize.

suggests keywords to include on specific pages

suggests backlinks to go after

UX issues can be flagged here too – connect Google analytics. Check out pages with high bounce rate and short time on site.

Organic Traffic insights

Connect Google analytics and search console

Here, you can see pages with good and bad UX metrics and the keywords driving traffic

You can also see which keywords each page ranks for.

Use this report to decide if you should include other keywords on a given page – in the page’s existing content, or create new, more specific pages.

Domain overview

Key metrics, shown at the top include bounce rate and average time on site.

You can see your own, or competitors top pages in terms of traffic

See traffic sources- what percentage of traffic is direct, coming from social media etc. (you may have to click on “Traffic Analytics” in the side menu to see this)

You can compare domain metrics to another competitors, or your own website.

You can use Bulk Analysis to enter up to 100 domains to examine.

Keyword overview tool

Good for finding head/seed keywords.

Can view the Google SERPs and see what the competition for a given keyword is like

See what other keywords competitors are ranking for, on a given URL:

You can see questions related to/containing a given keyword

You can see international search volume for a keyword.

Related keywords are shown too – e.g. “football supplies” is related to “football equipment”

See SERP Features – what rich snippets etc are showing in the SERPs.

Keyword Magic tool

Start with a seed keyword

by default the report will give you broad match keywords.

Be sure to check out “related” match types (all keywords is also helpful)

For KW research – click on one of the main head KWs – and see what keywords your competitors rank for
(clicking on the keyword takes you to the Keyword Overview tool – scroll down to SERP Analysis)

You can add your selected keywords to a list.

More info in the semrush blog post about the tool.

Content marketing

topic research – add a keyword to get topic ideas

For example, if you enter “football equipment” (in the US) the tool gives lots of article ideas around “shoulder pads”, “football helmets”, “football cleats” etc.

Sometimes you get random ideas that you would never have thought of, like the searches around the dog called “pickle” when you look for topic ideas about Pickleball

  • There is loads more you can do with SEMRush, including position tracking. I’ll cover these additional tools in dedicated blog posts.

Keyword Manager

If you have a big list of keywords you want to get search volume for, I like to click “create a regular list” and then name the list and click “Add keywords” in the top right of the screen

Also handy if you’ve done keyword research for one country and need the search volumes for another country