SEO in Laravel - Optimizing JavaScript files in a blog

Video thumbnail

I'll give you a few thoughts on using JavaScript and optimization in a Laravel project, but you can apply these steps to other types of web projects.

The first point I want to review is the blessed JavaScript, which seems incredible to me, but I have practically nothing loading. Still, I do have some active integrations, such as with Facebook and Google Tag Manager. These are ads that load on the website, but they are NOT NECESSARY for the website to function. This is a key factor, since we can load them LATER, after a reasonable amount of time.

Criticism of YouTube integration into websites

I also have YouTube integrated. And this seems incredible to me: that they themselves would criticize an integration they provide, that is, YouTube. You go to their platform, click share, place the iframe, and it's supposed to be optimized to pass its own tests... but it isn't. So, well, I have to come up with something out of the box.

So, I want to analyze what JavaScript is loading on my page. To do this, I go to the developer tools section, specifically the Network tab, and filter by .js. When I reload the page, I can clearly see the loaded scripts, and we'll see that the JS is a CRITICAL part, as it loads too many resources, costing the entire website.

Identifying my JavaScript

At this point, we're clear on the scripts that aren't part of the website, such as the Facebook pixel or Google Ads. We're also familiar with other resources, such as YouTube, that, while not part of the website, are necessary to create a more engaging resource.

The next step is to optimize our own JS files. In this case, there are two:

  • highlight.js
  • blog.js

Optimize our files

These two are my own. This is already an improvement, because before I had everything in a single file, the famous blog.js. It included everything. Based on recommendations, I decided to split them into smaller modules.

I'm also removing dependencies I no longer need. For example, Axios: I only make a few requests on the blog, and I've migrated most of them to fetch. There may still be some leftovers there, but little by little I'm getting everything organized.

Replacing Axios and other scripts

I've already completely removed Axios. I also considered removing Alpine, but it's so lightweight that I'll leave it for now.

Another script I'm considering is the highlight.js plugin. This is essential for me, as it formats the code in articles. Although I don't have an example here, it basically detects pre-blocks and applies visual formatting to them, separating them by tokens to stylize the code.

Code organization and conditional loading

What I'm doing is organizing the code better. For example, I have the highlight.js split: its CSS is in the main layout, and the JS is loaded only when needed, under a specific condition. I'll explain that logic in a future video.

The blog.js file now only contains the minimal functions: local searches and requests. It's less than 300 lines long, and I plan to optimize it further. I only keep the essentials.

YouTube Iframes are poison for your blog's SEO. How to fix them?

Video thumbnail

How I Went from a PageSpeed ​​of 37 to 100: The <iframe> Problem
In this and the next few videos, I'm going to tell you a little about how I went from having a PageSpeed ​​of 37 on my posts to simply reaching 100. Which, honestly, still surprises me quite a bit, since I still have a few things to fix. So, while the metric is perfect now, the site isn't entirely. I still have details to improve, as I've noted here, but anyway... I'll tell you a little about everything.

In this first video, I want to focus solely on the blessed YouTube <iframe>, which has been a real poison for performance.

The Blog Embedded Video Dilemma

If you have a blog, you've probably heard that you should share both text and video, especially if you create mixed content. That is, you create a video and then an article, or vice versa. Ideally, both should be presented in the same post: the video at the top and the text at the bottom. This way, you're boosting your content by offering two different formats.

So far, so good.
The problem comes when you decide to embed the video directly using YouTube's <iframe>, which is a real pain in terms of performance.

That was the problem I had with my blog, so I want to talk to you a little about it.

The Video That Is Not a Video

Notice that where the video should be, it doesn't look like a normal YouTube player. It's not a real video: what you're seeing is simply a static image. This image can be the same thumbnail as the video on YouTube or an optimized one that I uploaded directly to the blog.

In my case, I prefer to use the local image because it's in WebP format, which is what PageSpeed ​​requires. Oddly, YouTube returns a JPG as the thumbnail, and then Google tells you that you should use WebP. Ironic, isn't it?

Uploading Video on Demand

So what happens when you click on that image?
That's when the actual YouTube video loads, on demand. From the user's perspective, this is great because the page loads faster, but... there's one important detail.

When you do this, Google and other search engines no longer detect the video as rich content, because technically what you're showing at the beginning is just a disguised link.

Solution with Structured Data Tags

To solve this, I use an interesting technique. Even though the video isn't embedded directly in an <iframe>, I tell Google that the video is part of the content using a block of structured data in JSON-LD format.

In the source code, you can see that I use a script where I specify data like:

  • Video Title
  • Description (same as the post)
  • Publish Date
  • Author's Name (in my case, myself or "Desarrollo Libre")
  • Channel Logo
  • YouTube Video URL
const jsonLd = {
                    "@context": "https://schema.org",
                    "@type": "VideoObject",
                    "name": @json($post->title),
                    "description": @json($post->description ?? 'Resumen del contenido del video'),
                    "thumbnailUrl": image ??  `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
                    "uploadDate": "{{ \Carbon\Carbon::parse($post->date)->toAtomString() }}",
                    "embedUrl": `https://www.youtube.com/embed/${videoId}`,
                    "publisher": {
                        "@type": "Organization",
                        "name": "Desarrollo Libre",
                        "logo": {
                            "@type": "ImageObject",
                            "url": @json(asset('images/logo/logo.png'))
                        }
                    }
                };

And I put all of this together just once, when the page loads for the first time.

Alternatives: lite-youtube-embed, Plugins for YouTube

Another option I considered was using plugins like lite-youtube-embed, which loads videos 224 times faster, according to its documentation.

The problem is that you're bound to plugin-specific syntax, such as:

<lite-youtube videoid="TuIDDeVideo"></lite-youtube>

And that's exactly what I didn't want. I prefer to keep the content the way everyone else does—using <iframe>—and then process it internally. But if you want to go the plugin route, that's fine too.

SEO TRICK in Laravel: Lazy Loading and Pre-Rendering Content

Video thumbnail

I explained how I went from a PageSpeed ​​score of 50 to 73, simply by changing the blessed YouTube iframe. This was my initial score, and notice that when I changed it, I now have 73. Now I'm going to continue the explanation from there and show you how I got to 100, with the changes I made.

I'd also like to let you know that this material will be part of a course I'm going to create later. I'm currently working on my online store—at the time of recording—but later I'll create another course on how to create a well-optimized blog, explaining all these details and more.

Optimization: What did I change?

Improve accessibility, title, alt and derivatives

Here you can see the 73 again. From there, the rest of the changes focused on making loading more efficient. This included things like improving accessibility, for example, adding special attributes to buttons to indicate their function. This is especially helpful for screen readers, such as those used by people with visual impairments, so they understand what each button does:

Buttons don't have accessible names
Below are tips for improving the semantics of your app's controls. These tips can improve the experience for users of assistive technologies, such as screen readers.

<button aria-label="Close window">
<svg><!-- close icon --></svg>
</button>

I also made adjustments to the images, making sure to include the alt attribute with an appropriate description. And something very important: respect the heading hierarchy. If you add an H1, the next one should be an H2, then an H3, and so on. You shouldn't go from an H1 to an H3 directly, because it breaks the logic of the content. That was one of the details they were monitoring.

Optimize blocking resources

I also optimized the server's response. While changing the iframe helped a lot, I also started removing all the unnecessary JavaScript. There were things loading from the dashboard, meaningless. I eliminated all of that.

Reduced JavaScript and cleaner code

The JavaScript I was loading was having a significant impact. Here you can see how long the YouTube iframe was taking me: incredibly. And all these small changes directly affect how the page renders.

As you resolve these issues, loading improves, the server responds faster, and everything is processed more efficiently. So, with just a few strategic changes, the website evolved significantly.

The problem of excessive DOM

One of the things Google also pointed out to me was the excessive size of the DOM. This is because, for example, in my content I have two main types: one with the YouTube video and its transcript, and another with the courses.

When you open a course, the document is long because it contains all the course sections with their descriptions. In other words, I place all that structure within the HTML so Google understands what each thing does. That's why the DOM is so long.

This comes directly from my course platform, where each section has its own classes. I simply copied that structure to the blog post. So, even though it's long, it makes sense and is useful content.

Formatting and accessibility issues

There are some visual details I also need to improve. For example, some contrasts are very strong. I added a darker background, but it might be too much. I also need to reorganize the list so that recent posts appear first.

And something curious: when I use YouTube images, they return a JPG, not a WebP, which is what Google prefers. And that's despite the fact that if you upload an image to YouTube and right-click to download it, you often get a WebP. I don't know why they don't do this by default... it seems they do it on purpose to make our lives more complicated.

JavaScript Minification: In-Depth
Back to the key aspects: reducing JavaScript was essential. Also, taking care of the names and attributes. For example, images should have their alt attribute, and buttons should have their aria-label, which I didn't even know about, but which serves to indicate what each button does.

What I want to explain most is how I made everything as natural as possible. If a post has a YouTube video, I just place the iframe and that's it.

Source code and Highlight.js

This blog is for programmers, so obviously there are parts of code. I'm working in parallel on the books, which have a lot of code. For that, I use highlight.js, which stylizes the code and displays it beautifully.

The problem is that when you have a book with 300 pages, the plugin becomes cumbersome. At some point, it stops working or slows down significantly. This also affects page loading.

That's why I did something key: I processed the content only once and saved it in a field called final_content. This means that Highlight.js only runs once, and then it's no longer needed. This saves me the entire load of the plugin.

Alpine.js and minimal scripts

Another area I optimized was script loading. I use Alpine.js for the hamburger menu and share buttons. But even though it's light (about 8KB), I also decided to delay its loading.

What's the minimum my website needs to load? CSS, definitely. HTML alone isn't enough. Neither is the CSS from the plugin that styles the code. Google doesn't like sudden style changes, so that CSS must be loaded from the beginning.

I agree to receive announcements of interest about this Blog.

We talked about the importance of keeping in mind which JS (and other resources) are being used on the website you want to rank on Google.

- Andrés Cruz

En español